From 6ec7fcba88431860788582773563d34d8d48e335 Mon Sep 17 00:00:00 2001 From: Craig Beck Date: Fri, 24 May 2024 12:26:46 -0700 Subject: [PATCH 1/7] Add jsdoc comments; rename function args to match --- package.json | 2 +- src/Backend.ts | 16 ++++++++++++++++ src/Model/Model.ts | 15 ++++++++++++++- src/Model/contexts.ts | 36 +++++++++++++++++++++++++----------- src/Model/mutators.ts | 43 +++++++++++++++++++++++++++++++++++++++++-- src/Model/paths.ts | 6 ++++++ src/index.ts | 12 ++++++++++++ src/util.ts | 24 +++++++++++++++++++++++- 8 files changed, 138 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index dbf3f0e7..b8608e2b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "publishConfig": { "access": "public" }, - "version": "2.0.1", + "version": "2.1.0", "main": "./lib/index.js", "files": [ "lib/*" diff --git a/src/Backend.ts b/src/Backend.ts index 6b88cd15..82d63666 100644 --- a/src/Backend.ts +++ b/src/Backend.ts @@ -3,6 +3,9 @@ import * as util from './util'; import { ModelOptions, RootModel } from './Model/Model'; import Backend = require('sharedb'); +/** + * RacerBackend extends ShareDb Backend + */ export class RacerBackend extends Backend { racer: any; modelOptions: any; @@ -17,6 +20,13 @@ export class RacerBackend extends Backend { }); } + /** + * Create new `RootModel` + * + * @param options - optional model options + * @param request - optional request context See {@link sharedb.listen} for details. + * @returns a new root model + */ createModel(options?: ModelOptions, req?: any) { if (this.modelOptions) { options = (options) ? @@ -29,6 +39,12 @@ export class RacerBackend extends Backend { return model; }; + /** + * Model middleware that creates and attaches a `Model` to the `request` + * and attaches listeners to response for closing model on response completion + * + * @returns an Express middleware function + */ modelMiddleware() { var backend = this; function modelMiddleware(req, res, next) { diff --git a/src/Model/Model.ts b/src/Model/Model.ts index 35b56469..7595ed4b 100644 --- a/src/Model/Model.ts +++ b/src/Model/Model.ts @@ -28,6 +28,9 @@ declare module './Model' { type ModelInitFunction = (instance: RootModel, options: ModelOptions) => void; +/** + * Base class for Racer models + */ export class Model { static INITS: ModelInitFunction[] = []; @@ -45,7 +48,11 @@ export class Model { _preventCompose: boolean; _silent: boolean; - + /** + * Creates a new Racer UUID + * + * @returns a new Racer UUID. + * */ id(): UUID { return uuidv4(); } @@ -55,6 +62,9 @@ export class Model { }; } +/** + * RootModel is the model that holds all data and maintains connection info + */ export class RootModel extends Model { backend: RacerBackend; connection: Connection; @@ -70,6 +80,9 @@ export class RootModel extends Model { } } +/** + * Model for some subset of the data + */ export class ChildModel extends Model { constructor(model: Model) { super(); diff --git a/src/Model/contexts.ts b/src/Model/contexts.ts index ffd9763f..95fdaf46 100644 --- a/src/Model/contexts.ts +++ b/src/Model/contexts.ts @@ -24,8 +24,22 @@ declare module './Model' { * @see https://derbyjs.github.io/derby/models/contexts */ context(contextId: string): ChildModel; - getOrCreateContext(id: string): Context; - setContext(id: string): void; + /** + * Get the named context or create a new named context if it doesnt exist. + * + * @param contextId context id + * + * @see https://derbyjs.github.io/derby/models/contexts + */ + getOrCreateContext(contextId: string): Context; + /** + * Set the named context to use for this model. + * + * @param contextId context id + * + * @see https://derbyjs.github.io/derby/models/contexts + */ + setContext(contextId: string): void; /** * Unloads data for this model's context, or for a specific named context. @@ -52,24 +66,24 @@ Model.INITS.push(function(model) { model.root.setContext('root'); }); -Model.prototype.context = function(id) { +Model.prototype.context = function(contextId) { var model = this._child(); - model.setContext(id); + model.setContext(contextId); return model; }; -Model.prototype.setContext = function(id) { - this._context = this.getOrCreateContext(id); +Model.prototype.setContext = function(contextId) { + this._context = this.getOrCreateContext(contextId); }; -Model.prototype.getOrCreateContext = function(id) { - var context = this.root._contexts[id] || - (this.root._contexts[id] = new Context(this, id)); +Model.prototype.getOrCreateContext = function(contextId) { + var context = this.root._contexts[contextId] || + (this.root._contexts[contextId] = new Context(this, contextId)); return context; }; -Model.prototype.unload = function(id) { - var context = (id) ? this.root._contexts[id] : this._context; +Model.prototype.unload = function(contextId) { + var context = (contextId) ? this.root._contexts[contextId] : this._context; context && context.unload(); }; diff --git a/src/Model/mutators.ts b/src/Model/mutators.ts index 55b4789e..0912e0cb 100644 --- a/src/Model/mutators.ts +++ b/src/Model/mutators.ts @@ -44,15 +44,38 @@ declare module './Model' { createNullPromised(value: any): Promise; createNullPromised(subpath: Path, value: any): Promise; _createNull(segments: Segments, value: any, cb?: ErrorCallback): void; - + + /** + * Adds a document to the collection, under an id subpath corresponding + * to either the document's `id` property if present, or else a new randomly + * generated id. + * + * If a callback is provided, it's called when the write is committed or + * fails. + * + * @param value - Document to add + * @param cb - Optional callback + */ add(value: any, cb?: ValueCallback): string; + /** + * Adds a document to the collection, under an id subpath corresponding + * to either the document's `id` property if present, or else a new randomly + * generated id. + * + * If a callback is provided, it's called when the write is committed or + * fails. + * + * @param path - Optional Collection under which to add the document + * @param value - Document to add + * @param cb - Optional callback + */ add(subpath: Path, value: any, cb?: ValueCallback): string; addPromised(value: any): Promise; addPromised(subpath: Path, value: any): Promise; _add(segments: Segments, value: any, cb?: ValueCallback): string; /** - * Deletes the value at this model's path or a relative subpath. + * Deletes the value at this relative subpath. * * If a callback is provided, it's called when the write is committed or * fails. @@ -61,7 +84,23 @@ declare module './Model' { * @returns the old value at the path */ del(subpath: Path, cb?: Callback): S | undefined; + /** + * Deletes the value at this model's path. + * + * If a callback is provided, it's called when the write is committed or + * fails. + * + * @returns the old value at the path + */ del(cb?: Callback): T | undefined; + /** + * Deletes the value at this relative subpath. If not provided deletes at model path + * + * Promise resolves when commit succeeds, rejected on commit failure. + * + * @param subpath - Optional subpath to delete + * @returns promise + */ delPromised(subpath?: Path): Promise; _del(segments: Segments, cb?: ErrorCallback): S; diff --git a/src/Model/paths.ts b/src/Model/paths.ts index 6f911abe..c6a31078 100644 --- a/src/Model/paths.ts +++ b/src/Model/paths.ts @@ -6,8 +6,14 @@ exports.mixin = {}; declare module './Model' { interface Model { + /** + * Returns a ChildModel scoped to a relative subpath under this model's path. + * + * @param subpath + */ at(): ChildModel; at(subpath: PathLike): ChildModel; + isPath(subpath: PathLike): boolean; leaf(path: string): string; parent(levels?: number): Model; diff --git a/src/index.ts b/src/index.ts index 9902df81..15e2d8c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,12 @@ export { export const racer = new Racer(); +/** + * Creates new RootModel + * + * @param data - Optional Data to initialize model with + * @returns RootModel + */ export function createModel(data?) { var model = new RootModel(); if (data) { @@ -36,6 +42,12 @@ export function createModel(data?) { return model; } +/** + * Creates racer backend. Can only be called in server process and throws error if called in browser. + * + * @param options - Optional + * @returns racer backend + */ export function createBackend(options?: BackendOptions) { const backendModule = util.serverRequire(module, './Backend'); if (backendModule == null) { diff --git a/src/util.ts b/src/util.ts index 66c40448..685d2e68 100644 --- a/src/util.ts +++ b/src/util.ts @@ -177,18 +177,40 @@ export function promisify(original) { return fn; } +/** + * Conditionally require module only if in server process. No-op when called in browser. + * + * @param module + * @param id + * @returns module or undefined + */ export function serverRequire(module, id) { if (!isServer) return; return module.require(id); } +/** + * Use plugin only if invoked in server process. + * + * @param module + * @param id + * @param options - Optional + * @returns + */ export function serverUse(module, id: string, options?: unknown) { if (!isServer) return this; var plugin = module.require(id); return this.use(plugin, options); } -export function use(plugin, options?: unknown) { +/** + * Use plugin + * + * @param plugin + * @param options - Optional options passed to plugin + * @returns + */ +export function use(plugin: (arg0: unknown, options?: unknown) => void, options?: unknown) { // Don't include a plugin more than once var plugins = this._plugins || (this._plugins = []); if (plugins.indexOf(plugin) === -1) { From ff5d0ca88ee9c236c889ab1bb67e75a62cacbfb3 Mon Sep 17 00:00:00 2001 From: Craig Beck Date: Wed, 12 Jun 2024 12:20:22 -0700 Subject: [PATCH 2/7] Update jsdoc; correct link refs; fix param names to match code --- src/Backend.ts | 21 +++++++++++++------- src/Model/Model.ts | 4 ++++ src/Model/collections.ts | 15 ++++++++++++-- src/Model/index.ts | 2 +- src/Model/mutators.ts | 17 ++++++++++++---- src/Model/paths.ts | 43 ++++++++++++++++++++++++++++++++++++++-- src/Model/ref.ts | 31 +++++++++++++++++++++++------ src/index.ts | 8 +++----- src/util.ts | 24 ++++++++++++++++++++++ 9 files changed, 138 insertions(+), 27 deletions(-) diff --git a/src/Backend.ts b/src/Backend.ts index 82d63666..32415dc4 100644 --- a/src/Backend.ts +++ b/src/Backend.ts @@ -1,16 +1,23 @@ import * as path from 'path'; import * as util from './util'; -import { ModelOptions, RootModel } from './Model/Model'; +import { type ModelOptions, RootModel } from './Model/Model'; import Backend = require('sharedb'); +export type BackendOptions = { modelOptions?: ModelOptions } & Backend.ShareDBOptions; + /** * RacerBackend extends ShareDb Backend */ export class RacerBackend extends Backend { racer: any; - modelOptions: any; + modelOptions: ModelOptions; - constructor(racer: any, options?: { modelOptions?: ModelOptions } & Backend.ShareDBOptions) { + /** + * + * @param racer - Racer instance + * @param options - Model and SharedB options + */ + constructor(racer: any, options?: BackendOptions) { super(options); this.racer = racer; this.modelOptions = options && options.modelOptions; @@ -23,11 +30,11 @@ export class RacerBackend extends Backend { /** * Create new `RootModel` * - * @param options - optional model options - * @param request - optional request context See {@link sharedb.listen} for details. + * @param options - Optional model options + * @param request - Optional request context See {@link Backend.listen} for details. * @returns a new root model */ - createModel(options?: ModelOptions, req?: any) { + createModel(options?: ModelOptions, request?: any) { if (this.modelOptions) { options = (options) ? util.mergeInto(options, this.modelOptions) : @@ -35,7 +42,7 @@ export class RacerBackend extends Backend { } var model = new RootModel(options); this.emit('model', model); - model.createConnection(this, req); + model.createConnection(this, request); return model; }; diff --git a/src/Model/Model.ts b/src/Model/Model.ts index 7595ed4b..5befccc6 100644 --- a/src/Model/Model.ts +++ b/src/Model/Model.ts @@ -30,6 +30,8 @@ type ModelInitFunction = (instance: RootModel, options: ModelOptions) => void; /** * Base class for Racer models + * + * @typeParam T - Type of data the Model contains */ export class Model { static INITS: ModelInitFunction[] = []; @@ -82,6 +84,8 @@ export class RootModel extends Model { /** * Model for some subset of the data + * + * @typeParam T - type of data the ChildModel contains. */ export class ChildModel extends Model { constructor(model: Model) { diff --git a/src/Model/collections.ts b/src/Model/collections.ts index 871bcf99..d8da68ed 100644 --- a/src/Model/collections.ts +++ b/src/Model/collections.ts @@ -33,7 +33,19 @@ declare module './Model' { destroy(subpath?: Path): void; /** - * Gets the value located at this model's path or a relative subpath. + * Gets the value located at this model's path. + * + * If no value exists at the path, this returns `undefined`. + * + * _Note:_ The value is returned by reference, and object values should not + * be directly modified - use the Model mutator methods instead. The + * TypeScript compiler will enforce no direct modifications, but there are + * no runtime guards, which means JavaScript source code could still + * improperly make direct modifications. + */ + get(): ReadonlyDeep | undefined; + /** + * Gets the value located at a relative subpath. * * If no value exists at the path, this returns `undefined`. * @@ -45,7 +57,6 @@ declare module './Model' { * * @param subpath */ - get(): ReadonlyDeep | undefined; get(subpath?: Path): ReadonlyDeep | undefined; getCollection(collectionName: string): Collection; diff --git a/src/Model/index.ts b/src/Model/index.ts index 209cd3aa..435e907c 100644 --- a/src/Model/index.ts +++ b/src/Model/index.ts @@ -2,7 +2,7 @@ /// import { serverRequire } from '../util'; -export { Model, ChildModel, RootModel, ModelOptions, type UUID, type DefualtType } from './Model'; +export { Model, ChildModel, RootModel, type ModelOptions, type UUID, type DefualtType } from './Model'; export { ModelData } from './collections'; export { type Subscribable } from './subscriptions'; diff --git a/src/Model/mutators.ts b/src/Model/mutators.ts index 0912e0cb..f26d27e1 100644 --- a/src/Model/mutators.ts +++ b/src/Model/mutators.ts @@ -65,7 +65,7 @@ declare module './Model' { * If a callback is provided, it's called when the write is committed or * fails. * - * @param path - Optional Collection under which to add the document + * @param subpath - Optional Collection under which to add the document * @param value - Document to add * @param cb - Optional callback */ @@ -80,6 +80,7 @@ declare module './Model' { * If a callback is provided, it's called when the write is committed or * fails. * + * @typeParam S - Type of the data returned by delete operation * @param subpath * @returns the old value at the path */ @@ -90,6 +91,7 @@ declare module './Model' { * If a callback is provided, it's called when the write is committed or * fails. * + * @typeParam T - Type of the data returned by delete operation * @returns the old value at the path */ del(cb?: Callback): T | undefined; @@ -115,11 +117,17 @@ declare module './Model' { /** * Push a value to a model array * - * @param subpath * @param value * @returns the length of the array */ push(value: any): number; + /** + * Push a value to a model array at subpath + * + * @param subpath + * @param value + * @returns the length of the array + */ push(subpath: Path, value: any, cb?: ErrorCallback): number; pushPromised(value: any): Promise; pushPromised(subpath: Path, value: any): Promise; @@ -143,7 +151,7 @@ declare module './Model' { * * If a callback is provided, it's called when the write is committed or * fails. - * + * @typeParam V - type of data at subpath * @param subpath * @returns the removed item */ @@ -164,7 +172,8 @@ declare module './Model' { * If a callback is provided, it's called when the write is committed or * fails. * - * @param subpath + * @typeParam - Type of data targeted by remove operation + * @param subpath - Subpath to remove * @param index - 0-based index at which to start removing items * @param howMany - Number of items to remove. Defaults to `1`. * @returns array of the removed items diff --git a/src/Model/paths.ts b/src/Model/paths.ts index c6a31078..43dada9c 100644 --- a/src/Model/paths.ts +++ b/src/Model/paths.ts @@ -6,21 +6,60 @@ exports.mixin = {}; declare module './Model' { interface Model { + /** + * Returns a ChildModel scoped to the root path. + */ + at(): ChildModel; + /** * Returns a ChildModel scoped to a relative subpath under this model's path. * + * @typeParam S - type of data at subpath * @param subpath */ - at(): ChildModel; at(subpath: PathLike): ChildModel; - + + /** + * Check if subpath is a PathLike + * + * @param subpath + * @returns boolean + */ isPath(subpath: PathLike): boolean; + leaf(path: string): string; + + /** + * Get the parent {levels} up from current model or root model + * @param levels - number of levels to traverse the tree + * @returns parent or root model + */ parent(levels?: number): Model; + + /** + * Get full path to given subpath + * + * @param subpath - PathLike subpath + */ path(subpath?: PathLike): string; + + /** + * Returns a ChildModel scoped to the root path. + * + * @returns ChildModel + */ scope(): ChildModel; + + /** + * Returns a ChildModel scoped to an absolute path. + * + * @typeParam S - Type of data at subpath + * @param subpath - Path of GhildModel to scope to + * @returns ChildModel + */ scope(subpath: Path): ChildModel; + /** @private */ _splitPath(subpath: PathLike): string[]; } } diff --git a/src/Model/ref.ts b/src/Model/ref.ts index 4149bf8d..c349d03d 100644 --- a/src/Model/ref.ts +++ b/src/Model/ref.ts @@ -8,9 +8,20 @@ import type { Path, PathLike, Segments } from '../types'; type Refable = string | number | Model | Query | Filter; export interface RefOptions { + /** + * If true, indicies will be updated. + */ updateIndices: boolean; } +export interface RefListOptions { + /** + * If true, then objects from the source collection will be deleted if the + * corresponding item is removed from the refList's output path. + */ + deleteRemoved: boolean, +} + declare module './Model' { interface Model { /** @@ -24,18 +35,26 @@ declare module './Model' { * object, where each id string key maps to an object value with matching * `id` property. * @param idsPath - Path to an array of string ids - * @param options - * @param options.deleteRemoved - If true, then objects from the source - * collection will be deleted if the corresponding item is removed from - * the refList's output path + * @param options - Optional * * @see https://derbyjs.github.io/derby/models/refs */ - refList(outputPath: PathLike, collectionPath: PathLike, idsPath: PathLike, options?: { deleteRemoved?: boolean }): ChildModel; + refList(outputPath: PathLike, collectionPath: PathLike, idsPath: PathLike, options?: RefListOptions): ChildModel; _canRefTo(value: Refable): boolean; // _canRefTo(from: Segments, to: Segments, options: RefOptions): boolean; + /** + * Creates a reference for this model pointing to another path `to`. Like a + * symlink, any reads/writes on this `to` ref will work as if they were done on + * `path` directly. + * + * @param to - Location that the reference points to + * @return a model scoped to `path` + * + * @see https://derbyjs.github.io/derby/models/refs + */ + ref(to: PathLike): ChildModel; /** * Creates a reference at `path` pointing to another path `to`. Like a * symlink, any reads/writes on `path` will work as if they were done on @@ -44,11 +63,11 @@ declare module './Model' { * @param path - Location at which to create the reference. This must be * under a local collection, typically `'_page'` or a component model. * @param to - Location that the reference points to + * @params options - Optional {@link RefOptions} * @return a model scoped to `path` * * @see https://derbyjs.github.io/derby/models/refs */ - ref(to: PathLike): ChildModel; ref(path: PathLike, to: PathLike, options?: RefOptions): ChildModel; _ref(from: Segments, to: Segments, options?: RefOptions): void; diff --git a/src/index.ts b/src/index.ts index 15e2d8c9..8f5dd48a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,9 @@ import { Racer } from './Racer'; import * as util from './util'; import type { ShareDBOptions } from 'sharedb'; -import { type RacerBackend } from './Backend'; -import { RootModel, type ModelOptions } from './Model'; +export { type RacerBackend, type BackendOptions } from './Backend'; +import { RootModel } from './Model'; +import { type BackendOptions } from './Backend'; export { Query } from './Model/Query'; export { Model, ChildModel, ModelData, type UUID, type Subscribable, type DefualtType, type ModelOptions } from './Model'; @@ -15,11 +16,8 @@ export * as util from './util'; const { use, serverUse } = util; -export type BackendOptions = { modelOptions?: ModelOptions } & ShareDBOptions; - export { Racer, - RacerBackend, RootModel, use, serverUse, diff --git a/src/util.ts b/src/util.ts index 685d2e68..f593de63 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,6 +1,16 @@ + +/** @private */ export const deepEqual = require('fast-deep-equal'); + +/** + * Checks process.title is not equal to 'browser' + * + * Set as 'browser' via build tools (e.g. webpack) to package + * browser specific code to bundle + */ export const isServer = process.title !== 'browser'; +/** @private */ export function asyncGroup(cb) { var group = new AsyncGroup(cb); return function asyncGroupAdd() { @@ -39,17 +49,20 @@ class AsyncGroup { } } +/** @private */ function castSegment(segment: string | number): string | number { return (typeof segment === 'string' && isArrayIndex(segment)) ? +segment // sneaky op to convert numeric string to number : segment; } +/** @private */ export function castSegments(segments: Readonly>) { // Cast number path segments from strings to numbers return segments.map(segment => castSegment(segment)); } +/** @private */ export function contains(segments, testSegments) { for (var i = 0; i < segments.length; i++) { if (segments[i] !== testSegments[i]) return false; @@ -57,6 +70,7 @@ export function contains(segments, testSegments) { return true; } +/** @private */ export function copy(value) { if (value instanceof Date) return new Date(value); if (typeof value === 'object') { @@ -67,6 +81,7 @@ export function copy(value) { return value; } +/** @private */ export function copyObject(object) { var out = new object.constructor(); for (var key in object) { @@ -77,6 +92,7 @@ export function copyObject(object) { return out; } +/** @private */ export function deepCopy(value) { if (value instanceof Date) return new Date(value); if (typeof value === 'object') { @@ -99,19 +115,23 @@ export function deepCopy(value) { return value; } +/** @private */ export function equal(a, b) { return (a === b) || (equalsNaN(a) && equalsNaN(b)); } +/** @private */ export function equalsNaN(x) { // eslint-disable-next-line no-self-compare return x !== x; } +/** @private */ export function isArrayIndex(segment: string): boolean { return (/^[0-9]+$/).test(segment); } +/** @private */ export function lookup(segments: string[], value: unknown): unknown { if (!segments) return value; @@ -122,6 +142,7 @@ export function lookup(segments: string[], value: unknown): unknown { return value; } +/** @private */ export function mayImpactAny(segmentsList: string[][], testSegments: string[]) { for (var i = 0, len = segmentsList.length; i < len; i++) { if (mayImpact(segmentsList[i], testSegments)) return true; @@ -129,6 +150,7 @@ export function mayImpactAny(segmentsList: string[][], testSegments: string[]) { return false; } +/** @private */ export function mayImpact(segments: string[], testSegments: string[]): boolean { var len = Math.min(segments.length, testSegments.length); for (var i = 0; i < len; i++) { @@ -137,6 +159,7 @@ export function mayImpact(segments: string[], testSegments: string[]): boolean { return true; } +/** @private */ export function mergeInto(to, from) { for (var key in from) { to[key] = from[key]; @@ -144,6 +167,7 @@ export function mergeInto(to, from) { return to; } +/** @private */ export function promisify(original) { if (typeof original !== 'function') { throw new TypeError('The "original" argument must be of type Function'); From 5c2330d3c8e4f8f0784fdc71fbb0fe78e1629f06 Mon Sep 17 00:00:00 2001 From: Craig Beck Date: Wed, 12 Jun 2024 12:30:55 -0700 Subject: [PATCH 3/7] Add typedoc deps and plugin for excluding underscore prefixed symbols from docs --- package.json | 4 ++++ typedoc.json | 17 +++++++++++++++++ typedocExcludeUnderscore.mjs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 typedoc.json create mode 100644 typedocExcludeUnderscore.mjs diff --git a/package.json b/package.json index b8608e2b..e4b2e16f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ ], "scripts": { "build": "node_modules/.bin/tsc", + "docs": "node_modules/.bin/typedoc", "lint": "eslint .", "lint:fix": "eslint --fix .", "pretest": "npm run build", @@ -41,6 +42,9 @@ "eslint-config-google": "^0.14.0", "mocha": "^9.1.3", "nyc": "^15.1.0", + "typedoc": "^0.25.13", + "typedoc-plugin-mdn-links": "^3.1.28", + "typedoc-plugin-missing-exports": "^2.2.0", "typescript": "^5.1.3" }, "bugs": { diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 00000000..b6eb7747 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,17 @@ +{ + "entryPoints": ["src/index.ts"], + "excludeNotDocumented": false, + "plugin":[ + "typedoc-plugin-mdn-links", + "typedoc-plugin-missing-exports", + "./typedocExcludeUnderscore.mjs" + ], + "out": "docs/api", + "visibilityFilters": { + "protected": false, + "private": false, + "inherited": true, + "external": false + }, + "excludePrivate": true + } \ No newline at end of file diff --git a/typedocExcludeUnderscore.mjs b/typedocExcludeUnderscore.mjs new file mode 100644 index 00000000..22d1b708 --- /dev/null +++ b/typedocExcludeUnderscore.mjs @@ -0,0 +1,30 @@ +import { Converter, ReflectionFlag, ReflectionKind } from "typedoc"; +import camelCase from "camelcase"; + +/** + * @param {Readonly} app + */ +export function load(app) { + /** + * Create declaration event handler that sets symbols with underscore-prefixed names + * to private to exclude from generated documentation. + * + * Due to "partial class" style of code in use, otherwise private properties and methods - + * prefixed with underscore - are effectively declared public so they can be accessed in other + * files used to build class - e.g. Model. This marks anything prefixed with an underscore and + * no doc comment as private. + * + * @param {import('typedoc').Context} context + * @param {import('typedoc').DeclarationReflection} reflection + */ + function handleCreateDeclaration(context, reflection) { + if (!reflection.name.startsWith('_')) { + return; + } + if (!reflection.comment) { + reflection.setFlag(ReflectionFlag.Private); + } + } + + app.converter.on(Converter.EVENT_CREATE_DECLARATION, handleCreateDeclaration); +} \ No newline at end of file From d33c5065404b4d299371f60d61218ab4e5a796a7 Mon Sep 17 00:00:00 2001 From: Craig Beck Date: Wed, 12 Jun 2024 16:56:25 -0700 Subject: [PATCH 4/7] Additional jsdoc; subscriptions, ModelOptions --- src/Model/Model.ts | 10 ++ src/Model/subscriptions.ts | 206 ++++++++++++++++++++++++++++++++++--- src/util.ts | 2 + 3 files changed, 202 insertions(+), 16 deletions(-) diff --git a/src/Model/Model.ts b/src/Model/Model.ts index 5befccc6..c07777c4 100644 --- a/src/Model/Model.ts +++ b/src/Model/Model.ts @@ -11,14 +11,24 @@ export type DefualtType = unknown; declare module './Model' { interface DebugOptions { + /** Enables browser side logging of ShareDB operations */ debugMutations?: boolean, + /** Disable submitting of local operations to remote backend */ disableSubmit?: boolean, remoteMutations?: boolean, } interface ModelOptions { + /** see {@link DebugOptions} */ debug?: DebugOptions; + /** Ensure read-only access of model data */ fetchOnly?: boolean; + /** + * Delay in milliseconds before actuallyunloading data after `unload` called + * + * Default ot 0 on server, and 1000ms for browser. Runtime value can be inspected + * on {@link RootModel.unloadDelay} + */ unloadDelay?: number; bundleTimeout?: number; } diff --git a/src/Model/subscriptions.ts b/src/Model/subscriptions.ts index 2cb7bd78..ca6be621 100644 --- a/src/Model/subscriptions.ts +++ b/src/Model/subscriptions.ts @@ -16,21 +16,69 @@ declare module './Model' { /** * Retrieve data from the server, loading it into the model. * - * @param items - * @param cb + * @param items - Items to fetch + * @param cb - Callback called when operation completed * * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model */ fetch(items: Subscribable[], cb?: ErrorCallback): Model; + /** + * Retrieve data from the server, loading it into the model. + * + * @param item - Item to fetch + * @param cb - Callback called when operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ fetch(item: Subscribable, cb?: ErrorCallback): Model; + /** + * Retrieve data from the server, loading it into the model. + * + * @param cb - Callback called when operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ fetch(cb?: ErrorCallback): Model; + /** + * Promised version of {@link Model.fetch}. Instead of a callback, returns a promise + * that is resolved when operation completed + * + * @param items + */ fetchPromised(items: Subscribable[]): Promise; + /** + * Promised version of {@link Model.fetch}. Instead of a callback, returns a promise + * that is resolved when operation completed + * + * @param item + */ fetchPromised(item: Subscribable): Promise; + /** + * Promised version of {@link Model.fetch}. Instead of a callback, returns a promise + * that is resolved when operation completed + */ fetchPromised(): Promise; + /** + * Retrieve data from the server, loading it into the model. + * + * @param collecitonName - Name of colleciton to load item to + * @param id - Id of doc to load + * @param callback - Callback called when operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ fetchDoc(collecitonName: string, id: string, callback?: ErrorCallback): void; + /** + * Promised version of {@link Model.fetchDoc}. Instead of a callback, returns a promise + * that is resolved when operation completed + * + * @param collecitonName - Name of colleciton to load item to + * @param id - Id of doc to load + */ fetchDocPromised(collecitonName: string, id: string): Promise; + fetchOnly: boolean; /** @@ -40,60 +88,186 @@ declare module './Model' { * * Any item that's already subscribed will not result in a network call. * - * @param items - * @param cb + * @param items - Item to subscribe to + * @param cb - Callback called when operation completed * * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model */ subscribe(items: Subscribable[], cb?: ErrorCallback): Model; + /** + * Retrieve data from the server, loading it into the model. In addition, + * subscribe to the items, such that updates from any other client will + * automatically get reflected in this client's model. + * + * Any item that's already subscribed will not result in a network call. + * + * @param item - Item to subscribe to + * @param cb - Callback called when operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ subscribe(item: Subscribable, cb?: ErrorCallback): Model; + /** + * Retrieve data from the server, loading it into the model. In addition, + * subscribe to the items, such that updates from any other client will + * automatically get reflected in this client's model. + * + * Any item that's already subscribed will not result in a network call. + * + * @param cb - Callback called when operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ subscribe(cb?: ErrorCallback): Model; - + /** + * Promised version of {@link Model.subscribe}. Instead of a callback, returns a promise + * that is resolved when operation completed + * + * @param items - Items to subscribe to + */ subscribePromised(items: Subscribable[]): Promise; + /** + * Promised version of {@link Model.subscribe}. Instead of a callback, returns a promise + * that is resolved when operation completed + * + * @param item - Item to subscribe to + */ subscribePromised(item: Subscribable): Promise; + /** + * Promised version of {@link Model.subscribe}. Instead of a callback, returns a promise + * that is resolved when operation completed + */ subscribePromised(): Promise; subscribeDoc(collecitonName: string, id: string, callback?: ErrorCallback): void; subscribeDocPromised(collecitonName: string, id: string): Promise; /** - * The reverse of `#fetch`, marking the items as no longer needed in the + * The reverse of {@link Model.fetch}, marking the items as no longer needed in the * model. * - * @param items - * @param cb + * @param items - Items to unfetch + * @param cb - Optional Called after operation completed * * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model */ unfetch(items: Subscribable[], cb?: ErrorCallback): Model; + /** + * The reverse of {@link Model.fetch}, marking the items as no longer needed in the + * model. + * + * @param item - Item to unfetch + * @param cb - Optional Called after operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ unfetch(item: Subscribable, cb?: ErrorCallback): Model; + /** + * The reverse of {@link Model.fetch}, marking the items as no longer needed in the + * model. + * + * @param cb - Optional Called after operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ unfetch(cb?: ErrorCallback): Model; + /** + * Promised unfetch. See {@link Model.unfetch}. Instead of a callback, returns a promise + * that is resolved when operation completed + * + * @param items - Items to unfetch + * @returns Promise + */ unfetchPromised(items: Subscribable[]): Promise; + /** + * Promised unfetch. See {@link Model.unfetch}. Instead of a callback, returns a promise + * that is resolved when operation completed + * + * @param item - Item to unfetch + * @returns Promise + */ unfetchPromised(item: Subscribable): Promise; + /** + * Promised {@link Model.unfetch}. Instead of taking a callback, returns a promise + * that is resolved when operation completed + * + * @returns Promise + */ unfetchPromised(): Promise; - unfetchDoc(collecitonName: string, id: string, callback?: (err?: Error, count?: number) => void): void; - unfetchDocPromised(collecitonName: string, id: string): Promise; + /** + * Unfetch a document give collection name and document id + * + * @param collectionName - Collection name + * @param id - Document id to be unfeched + */ + unfetchDoc(collectionName: string, id: string, callback?: (err?: Error, count?: number) => void): void; + /** + * Promised {@link Model.unfetchDoc}. Instead of taking a callback, returns a promise + * that is resolved when operation completed + * + * @param collectionName - Collection name + * @param id - Document id to be unfeched + * @returns Promise + */ + unfetchDocPromised(collectionName: string, id: string): Promise; + /** + * Delay in milliseconds before model data actually unloaded after call to {@link Model.unload} + */ unloadDelay: number; - /** - * The reverse of `#subscribe`, marking the items as no longer needed in the + * The reverse of {@link Model.subscribe}, marking the items as no longer needed in the * model. * - * @param items - * @param cb + * @param items - The items to unsubscribe + * @param cb - Optional Called after operation completed * * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model */ unsubscribe(items: Subscribable[], cb?: ErrorCallback): Model; + /** + * The reverse of {@link Model.subscribe}, marking the items as no longer needed in the + * model. + * + * @param item - The item to unsubscribe + * @param cb - Optional Called after operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ unsubscribe(item: Subscribable, cb?: ErrorCallback): Model; + /** + * The reverse of {@link Model.subscribe}, marking the items as no longer needed in the + * model. + * + * @param cb - Optional Called after operation completed + * + * @see https://derbyjs.github.io/derby/models/backends#loading-data-into-a-model + */ unsubscribe(cb?: ErrorCallback): Model; + /** + * Promised version of {@link Model.unsubscribe}. Instead of taking a callback, returns a promise + * that is resolved when operation completed + */ unsubscribePromised(): Promise; - unsubscribeDoc(collecitonName: string, id: string, callback?: (err?: Error, count?: number) => void): void; - unsubscribeDocPromised(collecitonName: string, id: string): Promise; + + /** + * Unsubscribe document by collection name and id + * + * @param collectionName - Name of collection containting document + * @param id - Document id to unsubscribe + * @param callback - Optional Called after operation completed + */ + unsubscribeDoc(collectionName: string, id: string, callback?: (err?: Error, count?: number) => void): void; + /** + * Promised version of {@link Model.unsubscribeDoc} + * + * @param collectionName - Name of collection containting document + * @param id - Document id to unsbscribe + */ + unsubscribeDocPromised(collectionName: string, id: string): Promise; _fetchedDocs: CollectionCounter; _forSubscribable(argumentsObject: any, method: any): void; diff --git a/src/util.ts b/src/util.ts index f593de63..9774563f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -7,6 +7,8 @@ export const deepEqual = require('fast-deep-equal'); * * Set as 'browser' via build tools (e.g. webpack) to package * browser specific code to bundle + * + * see {@link https://github.com/derbyjs/derby-webpack/blob/main/createConfig.js#L95 | derby-webpack} */ export const isServer = process.title !== 'browser'; From 474d1ec41e6708618034f78841e6ba24c859fd19 Mon Sep 17 00:00:00 2001 From: Craig Beck Date: Wed, 12 Jun 2024 17:06:14 -0700 Subject: [PATCH 5/7] Link model and query --- src/Model/subscriptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/subscriptions.ts b/src/Model/subscriptions.ts index ca6be621..420d6144 100644 --- a/src/Model/subscriptions.ts +++ b/src/Model/subscriptions.ts @@ -7,7 +7,7 @@ const UnloadEvent = mutationEvents.UnloadEvent; const promisify = util.promisify; /** - * A path string, a `Model`, or a `Query`. + * A path string, a {@link Model}, or a {@link Query}. */ export type Subscribable = string | Model | Query; From 7a8733ca9d9a5e040fe3ae1d7db10539b4c089ad Mon Sep 17 00:00:00 2001 From: Craig Beck Date: Wed, 12 Jun 2024 17:06:33 -0700 Subject: [PATCH 6/7] Change output doc directory --- typedoc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typedoc.json b/typedoc.json index b6eb7747..f7968cc0 100644 --- a/typedoc.json +++ b/typedoc.json @@ -6,7 +6,7 @@ "typedoc-plugin-missing-exports", "./typedocExcludeUnderscore.mjs" ], - "out": "docs/api", + "out": "docs", "visibilityFilters": { "protected": false, "private": false, From 42a44e27aaeb5fa633a3e03ef43ecbd0063d283c Mon Sep 17 00:00:00 2001 From: Craig Beck Date: Fri, 14 Jun 2024 13:24:05 -0700 Subject: [PATCH 7/7] Move ReflitOptions --- src/Backend.ts | 2 +- src/Model/Model.ts | 4 ++-- src/Model/ref.ts | 9 +-------- src/Model/refList.ts | 16 ++++++++++++---- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Backend.ts b/src/Backend.ts index 32415dc4..2282b34f 100644 --- a/src/Backend.ts +++ b/src/Backend.ts @@ -47,7 +47,7 @@ export class RacerBackend extends Backend { }; /** - * Model middleware that creates and attaches a `Model` to the `request` + * Model middleware that creates and attaches a {@link Model} to the `request` * and attaches listeners to response for closing model on response completion * * @returns an Express middleware function diff --git a/src/Model/Model.ts b/src/Model/Model.ts index c07777c4..675a328a 100644 --- a/src/Model/Model.ts +++ b/src/Model/Model.ts @@ -24,9 +24,9 @@ declare module './Model' { /** Ensure read-only access of model data */ fetchOnly?: boolean; /** - * Delay in milliseconds before actuallyunloading data after `unload` called + * Delay in milliseconds before actually unloading data after `unload` called * - * Default ot 0 on server, and 1000ms for browser. Runtime value can be inspected + * Default to 0 on server, and 1000ms for browser. Runtime value can be inspected * on {@link RootModel.unloadDelay} */ unloadDelay?: number; diff --git a/src/Model/ref.ts b/src/Model/ref.ts index c349d03d..530aff53 100644 --- a/src/Model/ref.ts +++ b/src/Model/ref.ts @@ -4,6 +4,7 @@ import { Model } from './Model'; import { type Filter } from './filter'; import { type Query } from './Query'; import type { Path, PathLike, Segments } from '../types'; +import { type RefListOptions } from './refList'; type Refable = string | number | Model | Query | Filter; @@ -14,14 +15,6 @@ export interface RefOptions { updateIndices: boolean; } -export interface RefListOptions { - /** - * If true, then objects from the source collection will be deleted if the - * corresponding item is removed from the refList's output path. - */ - deleteRemoved: boolean, -} - declare module './Model' { interface Model { /** diff --git a/src/Model/refList.ts b/src/Model/refList.ts index 74e7d2cc..e4cfabab 100644 --- a/src/Model/refList.ts +++ b/src/Model/refList.ts @@ -2,10 +2,18 @@ import { EventListenerTree } from './EventListenerTree'; import { EventMapTree } from './EventMapTree'; import { Model } from './Model'; +export interface RefListOptions { + /** + * If true, then objects from the source collection will be deleted if the + * corresponding item is removed from the refList's output path. + */ + deleteRemoved: boolean, +} + declare module './Model' { interface Model { - refList(to: any, ids: any, options?: any): RefList; - refList(from: any, to: any, ids: any, options?: any): RefList; + refList(to: any, ids: any, options?: RefListOptions): RefList; + refList(from: any, to: any, ids: any, options?: RefListOptions): RefList; } } @@ -385,10 +393,10 @@ export class RefList{ fromSegments: any; toSegments: any; idsSegments: any; - options: any; + options?: RefListOptions; deleteRemoved: boolean; - constructor(model: Model, from, to, ids, options) { + constructor(model: Model, from, to, ids, options?: RefListOptions) { this.model = model && model.pass({$refList: this}); this.from = from; this.to = to;