@docstack/client 0.0.4 → 0.0.5

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 (54) hide show
  1. package/lib/core/attribute.js +406 -0
  2. package/lib/core/attribute.js.map +1 -0
  3. package/lib/core/class.d.ts +14 -15
  4. package/lib/core/class.js +761 -0
  5. package/lib/core/class.js.map +1 -0
  6. package/lib/core/crypto-engine/index.js +229 -0
  7. package/lib/core/crypto-engine/index.js.map +1 -0
  8. package/lib/core/crypto-engine/utils.js +88 -0
  9. package/lib/core/crypto-engine/utils.js.map +1 -0
  10. package/lib/core/datamodel/index.js +1308 -0
  11. package/lib/core/datamodel/index.js.map +1 -0
  12. package/lib/core/domain.d.ts +7 -8
  13. package/lib/core/domain.js +423 -0
  14. package/lib/core/domain.js.map +1 -0
  15. package/lib/core/index.js +520 -0
  16. package/lib/core/index.js.map +1 -0
  17. package/lib/core/job-engine/index.js +220 -0
  18. package/lib/core/job-engine/index.js.map +1 -0
  19. package/lib/core/policy-engine/index.js +232 -0
  20. package/lib/core/policy-engine/index.js.map +1 -0
  21. package/lib/core/query-engine/accumulators.js +258 -0
  22. package/lib/core/query-engine/accumulators.js.map +1 -0
  23. package/lib/core/query-engine/evaluator.js +179 -0
  24. package/lib/core/query-engine/evaluator.js.map +1 -0
  25. package/lib/core/query-engine/executor.js +405 -0
  26. package/lib/core/query-engine/executor.js.map +1 -0
  27. package/lib/core/query-engine/index.js +4 -0
  28. package/lib/core/query-engine/index.js.map +1 -0
  29. package/lib/core/query-engine/parser.js +515 -0
  30. package/lib/core/query-engine/parser.js.map +1 -0
  31. package/lib/core/query-engine/planner.js +330 -0
  32. package/lib/core/query-engine/planner.js.map +1 -0
  33. package/lib/core/stack.js +1817 -0
  34. package/lib/core/stack.js.map +1 -0
  35. package/lib/core/test-utils/docstack.js +222 -0
  36. package/lib/core/test-utils/docstack.js.map +1 -0
  37. package/lib/core/trigger/index.js +81 -0
  38. package/lib/core/trigger/index.js.map +1 -0
  39. package/lib/index.js +10 -4
  40. package/lib/index.js.map +1 -1
  41. package/lib/index.umd.js +10 -4
  42. package/lib/plugins/pouchdb.js +368 -0
  43. package/lib/plugins/pouchdb.js.map +1 -0
  44. package/lib/utils/crypto/index.js +34 -0
  45. package/lib/utils/crypto/index.js.map +1 -0
  46. package/lib/utils/index.js +58 -0
  47. package/lib/utils/index.js.map +1 -0
  48. package/lib/utils/logger/index.js +20 -0
  49. package/lib/utils/logger/index.js.map +1 -0
  50. package/lib/utils/logger/transport.js +28 -0
  51. package/lib/utils/logger/transport.js.map +1 -0
  52. package/lib/workers/dataModel.js +48 -0
  53. package/lib/workers/dataModel.js.map +1 -0
  54. package/package.json +2 -2
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Calculates a SHA-256 hash of the given content.
3
+ * Used to verify job content integrity.
4
+ * @param content - The string content to hash
5
+ * @returns The hex-encoded hash string
6
+ */
7
+ const calculateHash = async (content) => {
8
+ const getCrypto = () => globalThis.crypto;
9
+ const crypto = getCrypto();
10
+ const encoder = new TextEncoder();
11
+ const data = encoder.encode(content);
12
+ const buffer = await crypto.subtle.digest('SHA-256', data);
13
+ return Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0')).join('');
14
+ };
15
+ const now = () => Date.now();
16
+ /**
17
+ * Represents an executable job that can perform background tasks.
18
+ *
19
+ * Jobs are stored as documents with executable content that is verified
20
+ * via hash before execution. They support singleton mode (only one instance
21
+ * can run at a time), metadata persistence, and run history tracking.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // Jobs are typically executed via JobEngine
26
+ * const run = await stack.jobEngine.executeJob('Job-auth', {
27
+ * password: 'user-password',
28
+ * salt: 'user-salt'
29
+ * });
30
+ * console.log('Job result:', run.finalMetadata);
31
+ * ```
32
+ */
33
+ export class Job {
34
+ constructor(model, stack) {
35
+ this.model = model;
36
+ this.stack = stack;
37
+ }
38
+ /**
39
+ * Creates a validated Job instance from a JobModel document.
40
+ * Verifies the content hash to ensure integrity.
41
+ *
42
+ * @param model - The JobModel document from the database
43
+ * @param stack - The parent ClientStack instance
44
+ * @returns A validated Job instance
45
+ * @throws Error if the content hash doesn't match
46
+ */
47
+ static async create(model, stack) {
48
+ const computed = await calculateHash(model.content);
49
+ if (computed !== model.hash) {
50
+ throw new Error(`Job content hash mismatch for ${model._id}`);
51
+ }
52
+ return new Job(model, stack);
53
+ }
54
+ /**
55
+ * Hydrates the job content into an executable function.
56
+ * The job content must define an `execute(stack, params, job)` function.
57
+ */
58
+ hydrate() {
59
+ return new Function("stack", "params", "job", `"use strict"; ${this.model.content}; return execute(stack, params, job);`);
60
+ }
61
+ async hasRunningInstance() {
62
+ var _a;
63
+ if (!this.model.isSingleton)
64
+ return false;
65
+ const existingRuns = await this.stack.db.find({
66
+ selector: {
67
+ "~class": "~JobRun",
68
+ jobId: this.model._id,
69
+ status: "RUNNING",
70
+ },
71
+ limit: 1,
72
+ });
73
+ return Boolean((_a = existingRuns === null || existingRuns === void 0 ? void 0 : existingRuns.docs) === null || _a === void 0 ? void 0 : _a.length);
74
+ }
75
+ buildRun(triggerType, runtimeArgs) {
76
+ const getCrypto = () => globalThis.crypto;
77
+ const crypto = getCrypto();
78
+ const id = `JobRun-${crypto.randomUUID ? crypto.randomUUID() :
79
+ Array.from(crypto.getRandomValues(new Uint8Array(16))).map(b => b.toString(16).padStart(2, '0')).join('')}`;
80
+ const base = {
81
+ _id: id,
82
+ "~class": "~JobRun",
83
+ jobId: this.model._id,
84
+ status: "PENDING",
85
+ triggerType,
86
+ startTime: now(),
87
+ runtimeArgs,
88
+ initialMetadata: this.model.metadata ? Object.assign({}, this.model.metadata) : undefined,
89
+ };
90
+ return base;
91
+ }
92
+ async persistRun(run) {
93
+ const existing = await this.stack.db.get(run._id).catch(() => null);
94
+ const doc = existing ? Object.assign(Object.assign({}, run), { _rev: existing._rev }) : run;
95
+ await this.stack.db.bulkDocs([doc]);
96
+ return doc;
97
+ }
98
+ async persistJobMetadata(metadata) {
99
+ if (!metadata)
100
+ return;
101
+ const current = await this.stack.db.get(this.model._id).catch(() => null);
102
+ const nextDoc = Object.assign(Object.assign({}, this.model), { _rev: current === null || current === void 0 ? void 0 : current._rev, metadata });
103
+ await this.stack.db.bulkDocs([nextDoc]);
104
+ }
105
+ /**
106
+ * Executes the job with optional runtime arguments.
107
+ *
108
+ * Creates a JobRun document to track execution, handles singleton logic,
109
+ * and persists any metadata updates from the job execution.
110
+ *
111
+ * @param runtimeArgs - Optional parameters to pass to the job
112
+ * @param triggerType - How the job was triggered ('manual', 'scheduled', etc.)
113
+ * @returns The completed JobRunModel with status and results
114
+ * @throws Error if the job is disabled or a singleton instance is already running
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const run = await job.execute({ userId: 'user-123' }, 'manual');
119
+ * if (run.status === 'SUCCESS') {
120
+ * console.log('Result:', run.finalMetadata);
121
+ * }
122
+ * ```
123
+ */
124
+ async execute(runtimeArgs, triggerType = "manual") {
125
+ let run = this.buildRun(triggerType, runtimeArgs);
126
+ run = await this.persistRun(run);
127
+ if (!this.model.isEnabled) {
128
+ run.status = "SKIPPED";
129
+ run.errorMessage = `Job ${this.model._id} is disabled`;
130
+ run.endTime = now();
131
+ run.durationMs = run.endTime - run.startTime;
132
+ await this.persistRun(run);
133
+ throw new Error(run.errorMessage);
134
+ }
135
+ if (await this.hasRunningInstance()) {
136
+ run.status = "SKIPPED";
137
+ run.errorMessage = `Job ${this.model._id} already has a running instance`;
138
+ run.endTime = now();
139
+ run.durationMs = run.endTime - run.startTime;
140
+ await this.persistRun(run);
141
+ throw new Error(run.errorMessage);
142
+ }
143
+ try {
144
+ run.status = "RUNNING";
145
+ run.startTime = now();
146
+ run = await this.persistRun(run);
147
+ const executor = this.hydrate();
148
+ const params = Object.assign(Object.assign({}, (this.model.defaultParams || {})), (runtimeArgs || {}));
149
+ const result = await executor(this.stack, params, this.model);
150
+ const finalMetadata = (result && result.metadata) || this.model.metadata;
151
+ if (finalMetadata) {
152
+ run.finalMetadata = finalMetadata;
153
+ this.model.metadata = finalMetadata;
154
+ await this.persistJobMetadata(finalMetadata);
155
+ }
156
+ run.status = "SUCCESS";
157
+ run.endTime = now();
158
+ run.durationMs = run.endTime - run.startTime;
159
+ return await this.persistRun(run);
160
+ }
161
+ catch (error) {
162
+ run.status = "FAILURE";
163
+ run.errorMessage = (error === null || error === void 0 ? void 0 : error.message) || String(error);
164
+ run.errorStack = error === null || error === void 0 ? void 0 : error.stack;
165
+ run.endTime = now();
166
+ run.durationMs = run.endTime - run.startTime;
167
+ return await this.persistRun(run);
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * Engine for executing background jobs in the DocStack system.
173
+ *
174
+ * The JobEngine provides a simple interface to execute jobs by their ID.
175
+ * Jobs are fetched from the database, validated, and executed with
176
+ * full run tracking and metadata persistence.
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * // Execute a job
181
+ * const run = await stack.jobEngine.executeJob('Job-process-data', {
182
+ * batchSize: 100
183
+ * });
184
+ *
185
+ * console.log('Status:', run.status);
186
+ * console.log('Duration:', run.durationMs, 'ms');
187
+ * ```
188
+ */
189
+ export class JobEngine {
190
+ /**
191
+ * Creates a new JobEngine instance.
192
+ * @param stack - The parent ClientStack instance
193
+ */
194
+ constructor(stack) {
195
+ this.stack = stack;
196
+ }
197
+ /**
198
+ * Executes a job by its document ID.
199
+ *
200
+ * @param jobId - The job document ID (e.g., 'Job-auth')
201
+ * @param runtimeArgs - Optional parameters to pass to the job
202
+ * @param triggerType - How the job was triggered (default: 'manual')
203
+ * @returns The completed JobRunModel with execution results
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * const authRun = await jobEngine.executeJob('Job-auth', {
208
+ * password: 'secret',
209
+ * salt: 'user-salt'
210
+ * });
211
+ * const derivedKey = authRun.finalMetadata?.derivedKey;
212
+ * ```
213
+ */
214
+ async executeJob(jobId, runtimeArgs, triggerType = "manual") {
215
+ const jobDoc = await this.stack.db.get(jobId);
216
+ const job = await Job.create(jobDoc, this.stack);
217
+ return job.execute(runtimeArgs, triggerType);
218
+ }
219
+ }
220
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/job-engine/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Set of system classes that bypass policy evaluation.
3
+ * These classes are internal to DocStack and always accessible.
4
+ */
5
+ const SYSTEM_CLASSES = new Set([
6
+ "~Policy",
7
+ "~Job",
8
+ "~JobRun",
9
+ "~AuthModule",
10
+ "~UserSession",
11
+ "class",
12
+ "domain"
13
+ ]);
14
+ /**
15
+ * Engine for evaluating access control policies on documents.
16
+ *
17
+ * PolicyEngine implements role-based access control (RBAC) by evaluating
18
+ * policy rules against documents and user sessions. Policies can be:
19
+ * - Class-level (apply to all documents of a class)
20
+ * - User-specific (apply only to a specific user)
21
+ * - Group-specific (apply only to users in a specific group)
22
+ *
23
+ * Policy rules are JavaScript expressions that receive the document,
24
+ * session, and groupId as context and return a boolean.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * // Policy engine is used automatically during document operations
29
+ * // Policies are defined as documents:
30
+ * await stack.createDoc(null, '~Policy', null, {
31
+ * name: 'user-read-own',
32
+ * targetClass: ['User'],
33
+ * rule: 'return document.userId === session.userId;'
34
+ * });
35
+ * ```
36
+ */
37
+ export class PolicyEngine {
38
+ /**
39
+ * Creates a new PolicyEngine instance.
40
+ * @param stack - The parent ClientStack instance
41
+ */
42
+ constructor(stack) {
43
+ this.stack = stack;
44
+ }
45
+ /**
46
+ * Gets the current authentication session proof.
47
+ * @returns The session proof, or undefined if not authenticated
48
+ */
49
+ getSessionProof() {
50
+ return this.stack.authSession;
51
+ }
52
+ /**
53
+ * Checks if a class should bypass policy evaluation.
54
+ * System classes (prefixed with ~) are always allowed.
55
+ *
56
+ * @param targetClass - The class name to check
57
+ * @returns `true` if the class should bypass policies
58
+ */
59
+ shouldBypass(targetClass) {
60
+ if (SYSTEM_CLASSES.has(targetClass))
61
+ return true;
62
+ const normalized = targetClass.startsWith("~") ? targetClass.slice(1) : `~${targetClass}`;
63
+ return SYSTEM_CLASSES.has(normalized);
64
+ }
65
+ async loadPolicies(targetClass, aliases = []) {
66
+ const identifiers = new Set([targetClass, ...aliases]);
67
+ const result = await this.stack.findDocuments({ "~class": "~Policy" });
68
+ return result.docs.filter((doc) => {
69
+ if (!Array.isArray(doc.targetClass))
70
+ return false;
71
+ return doc.targetClass.some((entry) => identifiers.has(entry));
72
+ });
73
+ }
74
+ getTargetIdentifiers(targetId, targetName) {
75
+ const identifiers = new Set();
76
+ const variants = [targetId, targetName];
77
+ for (const value of variants) {
78
+ identifiers.add(value);
79
+ if (value.startsWith("~")) {
80
+ identifiers.add(value.slice(1));
81
+ }
82
+ else {
83
+ identifiers.add(`~${value}`);
84
+ }
85
+ }
86
+ return Array.from(identifiers);
87
+ }
88
+ async resolveClassTarget(targetClass) {
89
+ var _a, _b;
90
+ const classModel = await this.stack.getClassModel(targetClass).catch(() => null);
91
+ return {
92
+ id: (_a = classModel === null || classModel === void 0 ? void 0 : classModel._id) !== null && _a !== void 0 ? _a : targetClass,
93
+ name: (_b = classModel === null || classModel === void 0 ? void 0 : classModel.name) !== null && _b !== void 0 ? _b : targetClass,
94
+ };
95
+ }
96
+ /**
97
+ * Evaluates a policy rule against a document and session.
98
+ * The rule is a JavaScript expression that returns a boolean.
99
+ *
100
+ * @param policy - The policy containing the rule
101
+ * @param document - The document being accessed
102
+ * @param session - The current auth session
103
+ * @returns Whether the rule permits access
104
+ */
105
+ async evaluateRule(policy, document, session) {
106
+ const executor = new Function("document", "session", "groupId", `"use strict"; ${policy.rule}`);
107
+ const result = executor(document || {}, session.session, session.session.groupId);
108
+ if (result instanceof Promise) {
109
+ return Boolean(await result);
110
+ }
111
+ return Boolean(result);
112
+ }
113
+ filterPoliciesForSession(policies, session) {
114
+ const sessionUserId = session.session.userId || session.session.username;
115
+ const sessionGroups = Array.isArray(session.session.groupId)
116
+ ? session.session.groupId
117
+ : session.session.groupId
118
+ ? [session.session.groupId]
119
+ : [];
120
+ return policies.filter((policy) => {
121
+ const matchesUser = !policy.userId || policy.userId === sessionUserId || policy.userId === session.session.username;
122
+ const matchesGroup = !policy.groupId || sessionGroups.includes(policy.groupId);
123
+ return matchesUser && matchesGroup;
124
+ });
125
+ }
126
+ async authorize(targetClass, operation, document) {
127
+ const { id: targetId, name: targetName } = await this.resolveClassTarget(targetClass);
128
+ const identifiers = this.getTargetIdentifiers(targetId, targetName);
129
+ if (this.shouldBypass(targetName)) {
130
+ return true;
131
+ }
132
+ const policies = await this.loadPolicies(targetId, identifiers);
133
+ if (policies.length === 0) {
134
+ return true;
135
+ }
136
+ const session = this.getSessionProof();
137
+ if (!session) {
138
+ throw new Error("Stack is not authenticated for policy evaluation");
139
+ }
140
+ const targetedPolicies = policies.filter((policy) => policy.userId || policy.groupId);
141
+ const basePolicies = policies.filter((policy) => !policy.userId && !policy.groupId);
142
+ const matchingPolicies = targetedPolicies.length > 0
143
+ ? this.filterPoliciesForSession(targetedPolicies, session)
144
+ : this.filterPoliciesForSession(basePolicies, session);
145
+ if (targetedPolicies.length > 0 && matchingPolicies.length === 0) {
146
+ throw new Error(`No matching policy allowed ${operation} on class '${targetClass}'`);
147
+ }
148
+ let allowed = false;
149
+ for (const policy of matchingPolicies) {
150
+ const result = await this.evaluateRule(policy, document, session);
151
+ if (result === false) {
152
+ throw new Error(`Policy '${policy._id}' denied ${operation} on class '${targetClass}'`);
153
+ }
154
+ if (result === true) {
155
+ allowed = true;
156
+ }
157
+ }
158
+ return allowed;
159
+ }
160
+ /**
161
+ * Ensures write access is allowed for a document of the given class.
162
+ * Throws an error if no policy permits the write operation.
163
+ *
164
+ * @param targetClass - The class of the document being written
165
+ * @param document - The document to write
166
+ * @throws Error if write is not permitted
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * // Called automatically during createDoc/updateCard operations
171
+ * await policyEngine.ensureWriteAllowed('Task', taskDocument);
172
+ * ```
173
+ */
174
+ async ensureWriteAllowed(targetClass, document) {
175
+ const allowed = await this.authorize(targetClass, "write", document);
176
+ if (!allowed) {
177
+ throw new Error(`No matching policy allowed write on class '${targetClass}'`);
178
+ }
179
+ }
180
+ /**
181
+ * Checks if a document is readable by the current user.
182
+ * Used to filter query results based on read policies.
183
+ *
184
+ * @param document - The document to check
185
+ * @returns `true` if the document can be read
186
+ * @throws Error if the stack is not authenticated
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * // Used internally during findDocuments
191
+ * const readable = await policyEngine.isReadableDocument(doc);
192
+ * if (readable) {
193
+ * results.push(doc);
194
+ * }
195
+ * ```
196
+ */
197
+ async isReadableDocument(document) {
198
+ const targetClass = document === null || document === void 0 ? void 0 : document["~class"];
199
+ const { id: targetId, name: targetName } = await this.resolveClassTarget(targetClass);
200
+ if (!targetClass || this.shouldBypass(targetName)) {
201
+ return true;
202
+ }
203
+ const policies = await this.loadPolicies(targetId, this.getTargetIdentifiers(targetId, targetName));
204
+ if (policies.length === 0) {
205
+ return true;
206
+ }
207
+ const session = this.getSessionProof();
208
+ if (!session) {
209
+ throw new Error("Stack is not authenticated for policy evaluation");
210
+ }
211
+ const targetedPolicies = policies.filter((policy) => policy.userId || policy.groupId);
212
+ const basePolicies = policies.filter((policy) => !policy.userId && !policy.groupId);
213
+ const matchingPolicies = targetedPolicies.length > 0
214
+ ? this.filterPoliciesForSession(targetedPolicies, session)
215
+ : this.filterPoliciesForSession(basePolicies, session);
216
+ if (targetedPolicies.length > 0 && matchingPolicies.length === 0) {
217
+ return false;
218
+ }
219
+ let permitted = false;
220
+ for (const policy of matchingPolicies) {
221
+ const result = await this.evaluateRule(policy, document, session);
222
+ if (result === false) {
223
+ return false;
224
+ }
225
+ if (result === true) {
226
+ permitted = true;
227
+ }
228
+ }
229
+ return permitted;
230
+ }
231
+ }
232
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/policy-engine/index.ts"],"names":[],"mappings":"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"}