@docstack/client 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +1 -1
  3. package/lib/core/attribute.d.ts +11 -2
  4. package/lib/core/class.d.ts +40 -6
  5. package/lib/core/content-transfer.d.ts +178 -0
  6. package/lib/core/crypto-engine/index.d.ts +82 -1
  7. package/lib/core/crypto-engine/utils.d.ts +36 -1
  8. package/lib/core/datamodel/index.d.ts +16 -0
  9. package/lib/core/domain.d.ts +1 -1
  10. package/lib/core/guarded-db.d.ts +52 -0
  11. package/lib/core/index.d.ts +106 -1
  12. package/lib/core/policy-engine/index.d.ts +35 -0
  13. package/lib/core/query-engine/classes.d.ts +24 -0
  14. package/lib/core/query-engine/executor.d.ts +11 -0
  15. package/lib/core/query-engine/index.d.ts +2 -1
  16. package/lib/core/query-engine/planner.d.ts +19 -0
  17. package/lib/core/stack.d.ts +676 -7
  18. package/lib/core/sync/class-filter.d.ts +106 -0
  19. package/lib/core/sync/filter-identity.d.ts +53 -0
  20. package/lib/core/sync/index.d.ts +334 -0
  21. package/lib/core/sync/internal-docs.d.ts +159 -0
  22. package/lib/index.d.ts +27 -1
  23. package/lib/index.js +12686 -4
  24. package/lib/index.umd.js +5017 -620
  25. package/lib/plugins/pouchdb.d.ts +43 -3
  26. package/lib/utils/logger/index.d.ts +28 -4
  27. package/lib/utils/logger/transport.d.ts +52 -11
  28. package/package.json +18 -10
  29. package/lib/core/attribute.js +0 -406
  30. package/lib/core/attribute.js.map +0 -1
  31. package/lib/core/class.js +0 -761
  32. package/lib/core/class.js.map +0 -1
  33. package/lib/core/crypto-engine/index.js +0 -229
  34. package/lib/core/crypto-engine/index.js.map +0 -1
  35. package/lib/core/crypto-engine/utils.js +0 -88
  36. package/lib/core/crypto-engine/utils.js.map +0 -1
  37. package/lib/core/datamodel/index.js +0 -1308
  38. package/lib/core/datamodel/index.js.map +0 -1
  39. package/lib/core/domain.js +0 -423
  40. package/lib/core/domain.js.map +0 -1
  41. package/lib/core/index.js +0 -532
  42. package/lib/core/index.js.map +0 -1
  43. package/lib/core/job-engine/index.js +0 -220
  44. package/lib/core/job-engine/index.js.map +0 -1
  45. package/lib/core/policy-engine/index.js +0 -232
  46. package/lib/core/policy-engine/index.js.map +0 -1
  47. package/lib/core/query-engine/accumulators.js +0 -258
  48. package/lib/core/query-engine/accumulators.js.map +0 -1
  49. package/lib/core/query-engine/evaluator.js +0 -179
  50. package/lib/core/query-engine/evaluator.js.map +0 -1
  51. package/lib/core/query-engine/executor.js +0 -405
  52. package/lib/core/query-engine/executor.js.map +0 -1
  53. package/lib/core/query-engine/index.js +0 -4
  54. package/lib/core/query-engine/index.js.map +0 -1
  55. package/lib/core/query-engine/parser.js +0 -515
  56. package/lib/core/query-engine/parser.js.map +0 -1
  57. package/lib/core/query-engine/planner.js +0 -330
  58. package/lib/core/query-engine/planner.js.map +0 -1
  59. package/lib/core/stack.js +0 -1826
  60. package/lib/core/stack.js.map +0 -1
  61. package/lib/core/test-utils/docstack.js +0 -222
  62. package/lib/core/test-utils/docstack.js.map +0 -1
  63. package/lib/core/trigger/index.js +0 -81
  64. package/lib/core/trigger/index.js.map +0 -1
  65. package/lib/index.js.map +0 -1
  66. package/lib/plugins/pouchdb.js +0 -412
  67. package/lib/plugins/pouchdb.js.map +0 -1
  68. package/lib/utils/crypto/index.js +0 -34
  69. package/lib/utils/crypto/index.js.map +0 -1
  70. package/lib/utils/index.js +0 -58
  71. package/lib/utils/index.js.map +0 -1
  72. package/lib/utils/logger/index.js +0 -20
  73. package/lib/utils/logger/index.js.map +0 -1
  74. package/lib/utils/logger/transport.js +0 -28
  75. package/lib/utils/logger/transport.js.map +0 -1
  76. package/lib/workers/dataModel.js +0 -48
  77. package/lib/workers/dataModel.js.map +0 -1
@@ -5,6 +5,8 @@ import { Trigger } from "./trigger/index.js";
5
5
  import { JobEngine } from "./job-engine/index.js";
6
6
  import Attribute from './attribute.js';
7
7
  import { AttributeType, ClientCredentials, StackConfig } from "@docstack/shared";
8
+ import { DocStackSyncHandle } from './sync/index.js';
9
+ import type { DocStackSyncOptions } from './sync/index.js';
8
10
  /**
9
11
  * The main entry point for the DocStack client library.
10
12
  *
@@ -43,8 +45,94 @@ declare class DocStack extends EventTarget {
43
45
  private store;
44
46
  /** Array of all initialized ClientStack instances. */
45
47
  stacks: ClientStack[];
48
+ /**
49
+ * Stacks currently being opened, by name.
50
+ *
51
+ * `stacks` only gains an entry once `ClientStack.create` resolves, so it cannot answer
52
+ * "is this one already on its way?". Without that, {@link addStack}'s guard is a check
53
+ * followed by an `await` - not atomic - and two concurrent callers each open a handle
54
+ * on the same database. See ADR-0022.
55
+ */
56
+ private pendingStacks;
57
+ /** The handle from the last {@link sync} call. */
58
+ private syncHandle?;
46
59
  private logger;
47
- private addStack;
60
+ /**
61
+ * Splits a {@link StackConfig} into the connection string and the options a stack
62
+ * is created with.
63
+ *
64
+ * Everything the caller passed is forwarded - including `adapter` and any
65
+ * adapter-specific keys - so a stack can be opened on a transport other than the
66
+ * default one.
67
+ *
68
+ * @param config - A stack name, or a full configuration object.
69
+ * @returns The connection string and the resolved {@link StackOptions}.
70
+ */
71
+ private static resolveStackConfig;
72
+ /**
73
+ * Opens a stack and adds it to this instance.
74
+ *
75
+ * Public because an application's set of databases is not always known at startup:
76
+ * a workspace joined at runtime needs its own database, and its own replication
77
+ * pair, without tearing down the stacks already open. Adding a stack that is
78
+ * already open returns the existing instance rather than opening a second handle
79
+ * on the same database.
80
+ *
81
+ * Dispatches `stack-added` with the new stack in `detail`.
82
+ *
83
+ * @param config - A stack name, or a full configuration object.
84
+ * @returns The stack, initialized and ready.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const stack = await docstack.addStack({ name: `ws-${workspace.slug}`, patches });
89
+ * await stack.sync({ remote: () => driveFor(workspace) });
90
+ * ```
91
+ */
92
+ addStack: (config: StackConfig) => Promise<ClientStack>;
93
+ /**
94
+ * Closes a stack and drops it from this instance.
95
+ *
96
+ * Closing cancels the stack's replication and releases its listeners; the data on
97
+ * disk is left alone unless `destroy` is set, which is what leaving a workspace for
98
+ * good looks like.
99
+ *
100
+ * Dispatches `stack-removed` with the stack's name in `detail`.
101
+ *
102
+ * @param name - The stack name or connection string.
103
+ * @param options - `destroy: true` also deletes the underlying database.
104
+ * @returns `true` if a stack was removed, `false` if there was none by that name.
105
+ */
106
+ removeStack: (name: string, options?: {
107
+ destroy?: boolean;
108
+ }) => Promise<boolean>;
109
+ /**
110
+ * Starts replication for every open stack against one transport.
111
+ *
112
+ * The `remote` resolver is called once per stack, which is what makes a
113
+ * database-per-workspace application a single call rather than a loop the
114
+ * application has to keep in step with its own stack list.
115
+ *
116
+ * @param options - See {@link DocStackSyncOptions}. `stacks` narrows it to a subset.
117
+ * @returns A handle holding every stack's replication.
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * const sync = await docstack.sync({
122
+ * remote: (stack) => new PouchDB(stack.name, { adapter: 'googledrive', accessToken }),
123
+ * });
124
+ * sync.addEventListener('status', () => render(sync.getStatus()));
125
+ * ```
126
+ */
127
+ sync: (options: DocStackSyncOptions) => Promise<DocStackSyncHandle>;
128
+ /**
129
+ * Returns the handle from the last {@link sync} call, or `null`.
130
+ */
131
+ getSyncHandle: () => DocStackSyncHandle | null;
132
+ /**
133
+ * Stops replication on every stack.
134
+ */
135
+ cancelSync: () => void;
48
136
  private initStacks;
49
137
  /**
50
138
  * Resets all initialized stacks.
@@ -185,4 +273,21 @@ declare class DocStack extends EventTarget {
185
273
  * @module @docstack/client
186
274
  */
187
275
  export { ClientStack, Trigger, Class, Attribute, Domain, JobEngine };
276
+ export { StackSyncHandle, DocStackSyncHandle, SyncSchemaMismatchError, SYNC_META_DOC_ID, readRemoteSchemaVersion, publishSchemaVersion, createReplicationFilter, isInternalDoc, resolveInternalClasses, createClassFilter, hasClassRules, DATA_MODEL_CLASSES, withFilterIdentity, describeFilter, INTERNAL_DOC_IDS, INTERNAL_DOC_ID_PREFIXES, INTERNAL_DOC_CLASSES, OPTIONAL_INTERNAL_DOC_CLASSES, } from "./sync/index.js";
277
+ export type { SyncDirection, SyncState, SyncStatus, StackSyncOptions, DocStackSyncOptions, RemoteResolver, SyncMetaDoc, InternalDocFilterOptions, ClassFilterOptions, } from "./sync/index.js";
278
+ export type { ClassBuildOptions } from "./class.js";
279
+ export { CONTENT_EXPORT_FORMAT, META_CLASSES, isContentClassName, isContentDocument, isContentRelation, stripTransientFields, } from "./content-transfer.js";
280
+ export type { ContentExport, ContentExportOptions, ContentImportOptions, ContentImportReport, ContentImportIssue, } from "./content-transfer.js";
281
+ export { SYSTEM_SEEDED_DOC_IDS } from "./datamodel/index.js";
282
+ export { collectQueryClasses } from "./query-engine/index.js";
283
+ export { StackWriteGuardError } from "./guarded-db.js";
284
+ export { StackLockedError } from "../plugins/pouchdb.js";
285
+ /**
286
+ * Key-identity helpers, for applications that re-key a database.
287
+ *
288
+ * `deriveKeyId` names a key the same way stored payloads do, so an application can tell
289
+ * which of its keys a field belongs to without holding any of them.
290
+ */
291
+ export { deriveKeyId, isEncryptedPayload } from "./crypto-engine/index.js";
292
+ export type { EncryptedPayload } from "./crypto-engine/index.js";
188
293
  export { DocStack };
@@ -28,11 +28,35 @@ export type PolicyOperation = "read" | "write";
28
28
  export declare class PolicyEngine {
29
29
  /** Reference to the parent stack for database and session access. */
30
30
  private readonly stack;
31
+ /**
32
+ * Every `~Policy` document, loaded once and reused across evaluations.
33
+ *
34
+ * `isReadableDocument` runs once per document a read returns, and it used to re-fetch
35
+ * the policy list from the database each time - the dominant cost of every read path.
36
+ * The list is invalidated on any write that touches a `~Policy` document: the write
37
+ * path calls {@link invalidatePolicyCache} synchronously (see StackPlugin), and the
38
+ * stack's shared changes feed calls it again for out-of-band writes such as another
39
+ * tab's. `null` means not loaded.
40
+ */
41
+ private allPoliciesCache;
42
+ /**
43
+ * Compiled policy rules, keyed by their source text.
44
+ *
45
+ * A rule is evaluated once per policy per document, and `new Function` is a full
46
+ * compile each time. The source text is the key - not the policy id - so two policies
47
+ * sharing a rule share the compilation, and an edited rule is simply a new key.
48
+ */
49
+ private compiledRules;
31
50
  /**
32
51
  * Creates a new PolicyEngine instance.
33
52
  * @param stack - The parent ClientStack instance
34
53
  */
35
54
  constructor(stack: ClientStack);
55
+ /**
56
+ * Drops the cached policy list so the next evaluation re-reads it.
57
+ * Called by the write path and the changes feed whenever a `~Policy` document lands.
58
+ */
59
+ invalidatePolicyCache(): void;
36
60
  /**
37
61
  * Gets the current authentication session proof.
38
62
  * @returns The session proof, or undefined if not authenticated
@@ -61,6 +85,17 @@ export declare class PolicyEngine {
61
85
  private evaluateRule;
62
86
  private filterPoliciesForSession;
63
87
  private authorize;
88
+ /**
89
+ * Whether any policy applies to a class - i.e. whether reads of it are filtered.
90
+ *
91
+ * Lets the query engine know when a database-level LIMIT is safe: with no
92
+ * applicable policies, no row fetched within the limit can be dropped afterwards.
93
+ * Cheap once the policy list is cached.
94
+ *
95
+ * @param targetClass - The class name or id.
96
+ * @returns `true` if at least one policy targets the class.
97
+ */
98
+ hasPoliciesFor(targetClass: string): Promise<boolean>;
64
99
  /**
65
100
  * Ensures write access is allowed for a document of the given class.
66
101
  * Throws an error if no policy permits the write operation.
@@ -0,0 +1,24 @@
1
+ import type { SelectAST, UnionAST } from "@docstack/shared";
2
+ /**
3
+ * Collects the class names a parsed query reads.
4
+ *
5
+ * Deliberately generic: rather than enumerating the node types that can hold a nested
6
+ * query - `scalar_subquery`, `exists_expr`, the right-hand side of `IN` - it walks the
7
+ * whole graph and harvests every `select` node it finds. A new subquery form is then
8
+ * covered on the day it is added, rather than on the day someone notices a stale list.
9
+ *
10
+ * @param ast - The `ast` from {@link ClientStack.query}.
11
+ * @returns The class names read, or `null` when the query reads a source that is not a
12
+ * named class. That distinction is the contract: `[]` means *this query reads nothing* -
13
+ * `SELECT 1` - and it is safe to subscribe to nothing; `null` means *I cannot account for
14
+ * this*, and the caller should fail open rather than watch an incomplete set.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { rows, ast } = await stack.query("SELECT * FROM Task JOIN Project ON …");
19
+ * collectQueryClasses(ast); // ["Task", "Project"]
20
+ * collectQueryClasses([]); // [] - reads nothing
21
+ * collectQueryClasses(null); // null - unknown, fail open
22
+ * ```
23
+ */
24
+ export declare const collectQueryClasses: (ast: (SelectAST | UnionAST)[] | null | undefined) => string[] | null;
@@ -12,3 +12,14 @@ import ClientStack from '../stack.js';
12
12
  * @throws {Error} If a table specified in the query is not found.
13
13
  */
14
14
  export declare function executePlan(stack: ClientStack, plan: any, params: any, outerRow?: any, outerAliases?: any): Promise<any>;
15
+ /**
16
+ * Executes a plan as an async stream of rows.
17
+ *
18
+ * A single-table plan with no aggregation, DISTINCT, ORDER BY, or subqueries streams
19
+ * for real: rows ride `findDocumentsIterator`'s keyset pages, get the WHERE re-check
20
+ * and projection per row, and honor OFFSET/LIMIT by counting - peak memory is one
21
+ * batch regardless of result size, and a LIMIT stops the underlying scan early. Any
22
+ * other plan falls back to `executePlan` and yields from the materialized result, so
23
+ * the API is uniform even where streaming isn't.
24
+ */
25
+ export declare function executePlanStream(stack: ClientStack, plan: any, params: any): AsyncGenerator<any, void, unknown>;
@@ -1,4 +1,5 @@
1
1
  export { parse } from "./parser.js";
2
2
  export type { SelectAST, UnionAST } from "@docstack/shared";
3
3
  export { createPlan } from "./planner.js";
4
- export { executePlan } from "./executor.js";
4
+ export { executePlan, executePlanStream } from "./executor.js";
5
+ export { collectQueryClasses } from "./classes.js";
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Whether a column name is safe to hand to Mango - as a selector key, a sort field,
3
+ * or a projection field. See {@link PUSHABLE_COLUMN_RE} and {@link UNPUSHABLE_COLUMNS}.
4
+ */
5
+ export declare function isPushableColumnName(name: any): boolean;
6
+ /**
7
+ * Checks if a predicate is a simple binary expression that can be converted
8
+ * directly into a Mango selector by `buildSelector`.
9
+ *
10
+ * This is the single authority on pushability - `buildSelector` imports it and refuses
11
+ * predicates that fail it, so the planner and the executor cannot drift apart and a
12
+ * predicate classified as pushed can never be silently dropped.
13
+ *
14
+ * @param {object} predicate The predicate AST node.
15
+ * @returns {boolean} True if the predicate is simple enough for pushdown.
16
+ */
17
+ export declare function isPushablePredicate(predicate: any): boolean;
1
18
  /**
2
19
  * Creates a logical query plan from a list of Abstract Syntax Trees (ASTs) for a UNION query.
3
20
  * @param {Array<object>} astList The list of ASTs generated by the parser.
@@ -18,10 +35,12 @@ export declare function createPlan(astList: any): {
18
35
  aggregation: any;
19
36
  orderBy: any;
20
37
  limit: any;
38
+ offset: any;
21
39
  } | {
22
40
  type: string;
23
41
  selectPlans: any[];
24
42
  unionOps: any[];
25
43
  orderBy: any;
26
44
  limit: any;
45
+ offset: any;
27
46
  };