@haverstack/core 0.7.0 → 0.9.0

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 (65) hide show
  1. package/README.md +30 -14
  2. package/dist/access.d.ts +20 -7
  3. package/dist/access.d.ts.map +1 -1
  4. package/dist/access.js +37 -16
  5. package/dist/access.js.map +1 -1
  6. package/dist/adapter-entry.d.ts +12 -0
  7. package/dist/adapter-entry.d.ts.map +1 -0
  8. package/dist/adapter-entry.js +11 -0
  9. package/dist/adapter-entry.js.map +1 -0
  10. package/dist/attachment-download.d.ts +75 -0
  11. package/dist/attachment-download.d.ts.map +1 -0
  12. package/dist/attachment-download.js +128 -0
  13. package/dist/attachment-download.js.map +1 -0
  14. package/dist/auth.d.ts +88 -0
  15. package/dist/auth.d.ts.map +1 -0
  16. package/dist/auth.js +145 -0
  17. package/dist/auth.js.map +1 -0
  18. package/dist/combine.d.ts.map +1 -1
  19. package/dist/combine.js +20 -3
  20. package/dist/combine.js.map +1 -1
  21. package/dist/did-entry.d.ts +12 -0
  22. package/dist/did-entry.d.ts.map +1 -0
  23. package/dist/did-entry.js +16 -0
  24. package/dist/did-entry.js.map +1 -0
  25. package/dist/did.d.ts +51 -0
  26. package/dist/did.d.ts.map +1 -0
  27. package/dist/did.js +153 -0
  28. package/dist/did.js.map +1 -0
  29. package/dist/id.d.ts +18 -6
  30. package/dist/id.d.ts.map +1 -1
  31. package/dist/id.js +39 -10
  32. package/dist/id.js.map +1 -1
  33. package/dist/index.d.ts +15 -10
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +14 -9
  36. package/dist/index.js.map +1 -1
  37. package/dist/merge.d.ts +11 -0
  38. package/dist/merge.d.ts.map +1 -0
  39. package/dist/merge.js +38 -0
  40. package/dist/merge.js.map +1 -0
  41. package/dist/schema.d.ts +31 -9
  42. package/dist/schema.d.ts.map +1 -1
  43. package/dist/schema.js +134 -11
  44. package/dist/schema.js.map +1 -1
  45. package/dist/stack.d.ts +748 -152
  46. package/dist/stack.d.ts.map +1 -1
  47. package/dist/stack.js +1805 -328
  48. package/dist/stack.js.map +1 -1
  49. package/dist/testing.d.ts +84 -23
  50. package/dist/testing.d.ts.map +1 -1
  51. package/dist/testing.js +260 -19
  52. package/dist/testing.js.map +1 -1
  53. package/dist/types.d.ts +281 -31
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js +14 -0
  56. package/dist/types.js.map +1 -1
  57. package/dist/validate.d.ts +26 -0
  58. package/dist/validate.d.ts.map +1 -1
  59. package/dist/validate.js +51 -1
  60. package/dist/validate.js.map +1 -1
  61. package/dist/wire-entry.d.ts +17 -0
  62. package/dist/wire-entry.d.ts.map +1 -0
  63. package/dist/wire-entry.js +24 -0
  64. package/dist/wire-entry.js.map +1 -0
  65. package/package.json +13 -1
package/dist/types.d.ts CHANGED
@@ -15,6 +15,20 @@ export type RecordId = string;
15
15
  export type TypeId = string;
16
16
  /** Opaque file identifier returned by putAttachment */
17
17
  export type FileId = string;
18
+ /**
19
+ * Reverse-DNS identifier for the software that wrote a Record, e.g.
20
+ * "com.example.myapp" — not a RecordId. Self-reported and never a
21
+ * permission input; `StackRecord.principalId` is the verified counterpart.
22
+ * See docs/spec/identity.md § App.
23
+ */
24
+ export type AppId = string;
25
+ /**
26
+ * Identifies a "who" — a DID string, e.g. "did:key:z6Mk...". A
27
+ * self-certifying identifier that means the same thing in every stack,
28
+ * unlike a RecordId. did:key is the mandatory floor method (see did.ts).
29
+ * See docs/spec/identity.md.
30
+ */
31
+ export type EntityId = string;
18
32
  export type TagAssociation = {
19
33
  kind: 'tag';
20
34
  label: string;
@@ -23,7 +37,6 @@ export type AttachmentAssociation = {
23
37
  kind: 'attachment';
24
38
  label: string;
25
39
  fileId: FileId;
26
- mimeType: string;
27
40
  };
28
41
  export type RelationshipAssociation = {
29
42
  kind: 'relationship';
@@ -40,12 +53,14 @@ export type Permission = {
40
53
  access: 'public';
41
54
  } | {
42
55
  access: 'entity';
43
- entityId: RecordId;
56
+ entityId: EntityId;
44
57
  read: boolean;
45
58
  write: boolean;
46
59
  } | {
47
60
  access: 'group';
48
61
  groupId: RecordId;
62
+ /** Restricts this entry to group admins. Absent = any member (member or admin). */
63
+ role?: 'admin';
49
64
  read: boolean;
50
65
  write: boolean;
51
66
  };
@@ -57,19 +72,31 @@ export type StackRecord = {
57
72
  content: Record<string, unknown>;
58
73
  version: number;
59
74
  parentId?: RecordId;
60
- entityId?: RecordId;
61
- appId?: RecordId;
75
+ entityId?: EntityId;
76
+ appId?: AppId;
77
+ /**
78
+ * The authenticated principal behind the write, when it isn't the author
79
+ * itself — a delegated app's own DID. Absent means the writer
80
+ * authenticated as the author, so `appId` is an unverifiable self-report;
81
+ * present means `appId` can be checked against the `_app` record naming
82
+ * this DID. See docs/spec/identity.md § App.
83
+ */
84
+ principalId?: EntityId;
62
85
  deletedAt?: Date;
63
86
  permissions?: Permission[];
64
87
  associations?: Association[];
65
88
  };
66
89
  export type RecordVersion = {
67
90
  version: number;
91
+ /** The record's typeId at the moment this version was snapshotted. */
92
+ typeId: TypeId;
68
93
  content: Record<string, unknown>;
69
94
  updatedAt: Date;
70
- entityId?: RecordId;
95
+ entityId?: EntityId;
96
+ associations?: Association[];
97
+ permissions?: Permission[];
71
98
  };
72
- export type ScalarFieldKind = 'string' | 'number' | 'boolean' | 'date' | 'text' | 'record-ref';
99
+ export type ScalarFieldKind = 'string' | 'number' | 'boolean' | 'date' | 'text' | 'record-ref' | 'file-ref';
73
100
  export type ScalarFieldDef = {
74
101
  kind: ScalarFieldKind;
75
102
  required?: boolean;
@@ -104,41 +131,82 @@ export type StackType = {
104
131
  migratesFrom?: TypeId;
105
132
  createdAt: Date;
106
133
  };
107
- /** Content for _entity records */
134
+ /**
135
+ * Content for _entity records: a stack-local profile card *about* a DID,
136
+ * not the identity itself. `name`/`handle` are this owner's local labels
137
+ * for that DID (the petname pattern), so two stacks holding different
138
+ * names for the same DID is correct. See docs/spec/identity.md § Entity.
139
+ */
108
140
  export type EntityContent = {
141
+ /**
142
+ * The identity this profile is about. e.g. "did:key:z6Mk...". A binding,
143
+ * not a value — a Record's `entityId` resolves through it to the name
144
+ * this card carries, so it is unique per stack and immutable once set.
145
+ * See docs/spec/identity.md § DID bindings.
146
+ */
147
+ did: string;
109
148
  /** Display name — human-friendly, not necessarily unique. May contain spaces and punctuation. e.g. "Jane Smith" */
110
149
  name: string;
111
- /** Short unique identifier within a namespace — URL-safe, no spaces. e.g. "janesmith". Like a username. Optional for private entities. */
150
+ /**
151
+ * Short, conventionally URL-safe label. e.g. "janesmith". A label, not a
152
+ * key: duplicates are legitimate and `did` is what identifies this
153
+ * profile. See docs/spec/identity.md § Entity.
154
+ */
112
155
  handle?: string;
113
156
  };
114
157
  /** Content for _app records */
115
158
  export type AppContent = {
159
+ /**
160
+ * The software this card is about, in reverse-DNS form — the value a
161
+ * Record's `appId` is checked against. Distinct from the card Record's
162
+ * own `appId`, which names whatever wrote the card (an admin console
163
+ * registering a third-party app is the ordinary case, and the two differ
164
+ * there). See docs/spec/identity.md § App.
165
+ */
166
+ appId: AppId;
116
167
  /** Display name of the app e.g. "My Notes App" */
117
168
  name: string;
169
+ /** Semver string e.g. "1.0.0". `appId` is the machine-readable identity, so no handle is needed here. */
170
+ version?: string;
118
171
  /**
119
- * Semver string e.g. "1.0.0". The app's unique machine-readable identity
120
- * is already captured by the _app record's appId (e.g. "com.example.myapp"),
121
- * so no handle is needed here.
172
+ * The DID this app authenticates with, when it holds a key of its own.
173
+ * Lets an attribution UI resolve a record's `principalId` to this card,
174
+ * and a server check a self-reported `appId` against the principal that
175
+ * wrote it. Absent for apps that ride their user's identity. Unique per
176
+ * stack and immutable once set, like `appId` above — see
177
+ * docs/spec/identity.md § DID bindings.
122
178
  */
123
- version?: string;
179
+ did?: string;
124
180
  };
125
181
  /** Content for _group records */
126
182
  export type GroupContent = {
127
183
  /** Display name — human-friendly, not necessarily unique. May contain spaces and punctuation. e.g. "Jane's Book Club" */
128
184
  name: string;
129
- /** Short unique identifier — URL-safe, no spaces. e.g. "janes-book-club". Useful for groups other people need to reference. Optional for private groups. */
185
+ /**
186
+ * Short, conventionally URL-safe label. e.g. "janes-book-club". A label,
187
+ * not a key: duplicates are legitimate, and a group is addressed by
188
+ * `stackUrl` when it has one and by its record id otherwise. See
189
+ * docs/spec/identity.md § Group.
190
+ */
130
191
  handle?: string;
131
192
  /** If present, this group owns a shared collaborative stack at this URL. Absent = permission-only group. */
132
193
  stackUrl?: string;
133
194
  };
134
- /** Actions that can be granted via a _grant record. */
135
- export type GrantAction = 'create' | 'read-own' | 'read-any' | 'update-own' | 'update-any' | 'delete-own' | 'delete-any';
195
+ /**
196
+ * Actions that can be granted via a _grant record. The array is the source
197
+ * of truth — GrantAction is derived from it so runtime validation (see
198
+ * Stack.grant()) can't drift from the type.
199
+ */
200
+ export declare const GRANT_ACTIONS: readonly ["create", "read-own", "read-any", "update-own", "update-any", "delete-own", "delete-any"];
201
+ export type GrantAction = (typeof GRANT_ACTIONS)[number];
136
202
  /** Content for _grant records */
137
203
  export type GrantContent = {
138
204
  /** Which record type the grant applies to. */
139
205
  typeId: TypeId;
140
206
  /** Which actions are permitted. */
141
207
  actions: GrantAction[];
208
+ /** Who the grant applies to. Absent = default grant, applies to any authenticated entity. */
209
+ granteeEntityId?: EntityId;
142
210
  };
143
211
  /** Content for _attachment records — one per upload, tracks file metadata. */
144
212
  export type AttachmentContent = {
@@ -153,10 +221,17 @@ export type AttachmentContent = {
153
221
  };
154
222
  /** Content for _config records — one singleton per stack, created on initialization. */
155
223
  export type ConfigContent = {
156
- /** Entity ID of the stack owner. */
157
- entityId: string;
158
- /** IANA timezone string e.g. "America/New_York". */
159
- timezone: string;
224
+ /**
225
+ * DID of the stack owner. Immutable — see docs/spec.md § The `_config`
226
+ * record for why ownership transfer isn't a field write.
227
+ */
228
+ entityId: EntityId;
229
+ /**
230
+ * IANA timezone string e.g. "America/New_York". Optional passthrough app
231
+ * metadata — nothing in core reads it for behavior, and there is no
232
+ * default. See docs/spec.md § The `_config` record.
233
+ */
234
+ timezone?: string;
160
235
  };
161
236
  /** Reserved system type IDs */
162
237
  export declare const SYSTEM_TYPES: {
@@ -176,9 +251,18 @@ export type DateRange = {
176
251
  };
177
252
  export type RecordFilter = {
178
253
  typeId?: TypeId | TypeId[];
254
+ /**
255
+ * Match every version of a type family — e.g. baseId: "com.example/note"
256
+ * matches both "com.example/note@1" and "com.example/note@2" records.
257
+ * Resolved against registered Types, not parsed from typeId strings, so
258
+ * it works regardless of which versions happen to exist. Combined with
259
+ * typeId (if both given) as an intersection.
260
+ */
261
+ baseId?: string | string[];
179
262
  parentId?: RecordId | null;
180
- appId?: RecordId | RecordId[];
181
- entityId?: RecordId | RecordId[];
263
+ appId?: AppId | AppId[];
264
+ entityId?: EntityId | EntityId[];
265
+ principalId?: EntityId | EntityId[];
182
266
  createdAt?: DateRange;
183
267
  updatedAt?: DateRange;
184
268
  tags?: string[];
@@ -201,6 +285,13 @@ export type StackQuery = {
201
285
  sort?: QuerySort;
202
286
  limit?: number;
203
287
  cursor?: string;
288
+ /**
289
+ * Records are returned exactly as stored by default ("stored"). Pass
290
+ * "latest" to apply the registered migration chain in memory before
291
+ * returning — never written back. Throws StackMigrationError if any
292
+ * matched record has no registered path to the latest version.
293
+ */
294
+ presentAt?: 'stored' | 'latest';
204
295
  };
205
296
  export type QueryResult = {
206
297
  records: StackRecord[];
@@ -222,39 +313,136 @@ export type Migration = {
222
313
  };
223
314
  export type AdapterCapabilities = {
224
315
  fullTextSearch: boolean;
316
+ /**
317
+ * Required `true` for local/in-process adapters; wire adapters may
318
+ * report `false`, driven by the server's discovery response.
319
+ * Stack.query() throws StackQueryError rather than silently widening.
320
+ * See docs/spec/adapters.md § Adapter capabilities.
321
+ */
225
322
  contentFieldQuery: boolean;
226
323
  sortableFields: Array<QuerySort['field']>;
324
+ /**
325
+ * Maximum attachment upload size in bytes, or `null` if unbounded. Lets
326
+ * apps pre-check and surface limits before burning an upload on a 413.
327
+ */
328
+ maxAttachmentBytes: number | null;
329
+ /**
330
+ * Maximum serialized size in bytes of a Record's content — a create body
331
+ * or a merge patch — or `null` if unbounded. The counterpart to
332
+ * maxAttachmentBytes for the JSON side of a write, which had no stated
333
+ * ceiling anywhere despite the spec bounding every other resource
334
+ * (validation depth, queryAllPages, GC grace).
335
+ *
336
+ * Local adapters declare `null`: nothing at the storage layer imposes
337
+ * one, and a caller with in-process access to the database can spend its
338
+ * own memory however it likes. A server declares its request-size limit
339
+ * here so apps can pre-check rather than discover it as a 413.
340
+ * See docs/spec/adapters.md § Adapter capabilities.
341
+ */
342
+ maxContentBytes: number | null;
227
343
  };
228
344
  /** What a Stack can do, as seen by app and plugin code. */
229
345
  export type StackFeatures = AdapterCapabilities;
230
346
  /**
231
- * The record-storage half of an adapter. Handles structured data, queries,
347
+ * Opt-in optimistic-concurrency precondition, accepted by every mutation
348
+ * that bumps a record's version. On mismatch the adapter throws
349
+ * StackVersionConflictError without applying anything. The check is atomic
350
+ * inside the adapter's write, never a read-then-write. See
351
+ * docs/spec/versioning.md § Optimistic concurrency (`ifVersion`).
352
+ */
353
+ export type ExpectedVersionOptions = {
354
+ expectedVersion?: number;
355
+ };
356
+ /**
357
+ * Accepted by every mutating StackRecordAdapter method. The adapter
358
+ * persists this prior-state snapshot as part of the SAME atomic write as
359
+ * the mutation, so a crash between the two can't leave an orphan versions
360
+ * row. Server-backed adapters (already atomic per-request) can ignore it.
361
+ */
362
+ export type SnapshotOptions = {
363
+ snapshot?: RecordVersion;
364
+ };
365
+ /**
366
+ * The record-storage half of an adapter: structured data, queries,
232
367
  * associations, versioning, type definitions, and stack identity.
233
368
  */
234
369
  export interface StackRecordAdapter {
235
370
  readonly capabilities: AdapterCapabilities;
236
- /** Entity ID of the stack owner. Set during adapter initialization. */
237
- readonly ownerEntityId: string;
238
- /** IANA timezone string for this stack e.g. "America/New_York". */
239
- readonly timezone: string;
371
+ /** DID of the stack owner. Set during adapter initialization. */
372
+ readonly ownerEntityId: EntityId;
373
+ /**
374
+ * IANA timezone string for this stack, or undefined if never set.
375
+ * Passthrough app metadata with no default — see docs/spec.md § The
376
+ * `_config` record.
377
+ */
378
+ readonly timezone: string | undefined;
379
+ /**
380
+ * Throws StackConflictError if `record.id` already exists — never a
381
+ * silent overwrite. The check must be atomic with the write (a PK/unique
382
+ * constraint or equivalent); Stack itself doesn't pre-check, so a raw
383
+ * adapter that skips this enforces nothing.
384
+ */
240
385
  createRecord(record: StackRecord): Promise<StackRecord>;
241
386
  getRecord(id: RecordId): Promise<StackRecord | null>;
242
- updateRecord(id: RecordId, changes: Partial<StackRecord>): Promise<StackRecord>;
387
+ /**
388
+ * Apply a content-only RFC 7396 merge patch: `null` removes a field,
389
+ * omitted fields are retained. Bumps `version`/`updatedAt` in the same
390
+ * write. Never touches `typeId` — a type change goes through
391
+ * commitMigration() instead.
392
+ */
393
+ patchContent(id: RecordId, patch: Record<string, unknown | null>, opts?: ExpectedVersionOptions & SnapshotOptions): Promise<StackRecord>;
243
394
  deleteRecord(id: RecordId, opts?: {
244
395
  hard?: boolean;
245
- }): Promise<void>;
396
+ } & ExpectedVersionOptions & SnapshotOptions): Promise<void>;
397
+ /** Reverse a soft delete. Returns the record as it now stands. */
398
+ undeleteRecord(id: RecordId, opts?: ExpectedVersionOptions & SnapshotOptions): Promise<StackRecord>;
246
399
  queryRecords(query: StackQuery): Promise<QueryResult>;
247
- associate(id: RecordId, association: Association): Promise<void>;
248
- dissociate(id: RecordId, association: Association): Promise<void>;
400
+ associate(id: RecordId, association: Association, opts?: ExpectedVersionOptions & SnapshotOptions): Promise<void>;
401
+ dissociate(id: RecordId, association: Association, opts?: ExpectedVersionOptions & SnapshotOptions): Promise<void>;
402
+ /** Replace all permissions on a record. Bumps version internally. */
403
+ setPermissions(id: RecordId, permissions: Permission[], opts?: ExpectedVersionOptions & SnapshotOptions): Promise<void>;
249
404
  getVersions(id: RecordId): Promise<RecordVersion[]>;
250
405
  getVersion(id: RecordId, version: number): Promise<RecordVersion | null>;
406
+ /**
407
+ * Standalone snapshot write, outside of a mutation's own atomic path.
408
+ * Mutating methods above take a `snapshot` option instead, so the
409
+ * snapshot and the mutation land in one write — see SnapshotOptions.
410
+ */
251
411
  saveVersion(id: RecordId, version: RecordVersion): Promise<void>;
412
+ /**
413
+ * Restore a record to a previous version's content (and associations,
414
+ * when the snapshot has them). Never restores permissions. Bumps version
415
+ * internally. Throws StackNotFoundError if the version doesn't exist.
416
+ */
417
+ restoreVersion(id: RecordId, version: number, opts?: ExpectedVersionOptions & SnapshotOptions): Promise<StackRecord>;
418
+ /**
419
+ * Commit a migration: write new content under a new typeId in one step.
420
+ * This is the only way a record's typeId changes after creation — used
421
+ * exclusively by Stack.migrateAll(); Stack.update() never changes typeId
422
+ * as a side effect. Bumps version internally.
423
+ */
424
+ commitMigration(id: RecordId, toTypeId: TypeId, content: Record<string, unknown>, opts?: SnapshotOptions): Promise<StackRecord>;
252
425
  saveType(type: StackType): Promise<void>;
253
426
  getType(id: TypeId): Promise<StackType | null>;
254
427
  listTypes(): Promise<StackType[]>;
428
+ /**
429
+ * Atomically verify fileId is unreferenced, then hard-delete its
430
+ * metadata records in the same adapter call, returning their ids.
431
+ * Throws StackConflictError if still referenced. Optional —
432
+ * Stack.deleteAttachment() has a non-atomic fallback. See
433
+ * docs/spec/attachments.md § Deleting attachments.
434
+ */
435
+ deleteUnreferencedAttachmentRecords?(fileId: FileId, metadataTypeId: TypeId): Promise<RecordId[]>;
255
436
  flush?(): Promise<void>;
256
437
  close?(): Promise<void>;
257
438
  }
439
+ /** One stored blob, as reported by StackBlobAdapter.listFiles(). */
440
+ export type BlobFileInfo = {
441
+ fileId: FileId;
442
+ size: number;
443
+ /** When the blob was written. Used to apply a GC grace period to fresh, not-yet-associated uploads. */
444
+ modifiedAt: Date;
445
+ };
258
446
  /**
259
447
  * The blob-storage half of an adapter. Handles raw binary data only;
260
448
  * attachment metadata lives on _attachment@1 records in the record adapter.
@@ -263,6 +451,13 @@ export interface StackBlobAdapter {
263
451
  putAttachment(data: Uint8Array): Promise<FileId>;
264
452
  getAttachment(fileId: FileId): Promise<Uint8Array>;
265
453
  deleteAttachment(fileId: FileId): Promise<void>;
454
+ /**
455
+ * Enumerate every blob currently in storage. Optional — without it,
456
+ * garbage collection can't find bare-bytes orphans (bytes with no
457
+ * metadata record at all). See docs/spec/attachments.md § Garbage
458
+ * collection.
459
+ */
460
+ listFiles?(): Promise<BlobFileInfo[]>;
266
461
  flush?(): Promise<void>;
267
462
  close?(): Promise<void>;
268
463
  }
@@ -271,5 +466,60 @@ export interface StackBlobAdapter {
271
466
  * Pass this to Stack.create(). Build one with combineAdapters() when you
272
467
  * want different backends for records and blobs (e.g. SQLite + S3).
273
468
  */
274
- export type StackAdapter = StackRecordAdapter & StackBlobAdapter;
469
+ export type StackAdapter = StackRecordAdapter & StackBlobAdapter & {
470
+ /**
471
+ * Store bytes and create the accompanying _attachment@1 record as one
472
+ * atomic operation. Optional; implement only when bytes and records
473
+ * live behind a single boundary (today: APIAdapter alone), and never
474
+ * synthesized by combineAdapters(). A security boundary, not an
475
+ * efficiency shortcut — see docs/spec/adapters.md § Interface split.
476
+ */
477
+ putAttachmentWithMetadata?(data: Uint8Array, mimeType: string, filename?: string, appId?: AppId): Promise<StackRecord>;
478
+ };
479
+ /**
480
+ * The two identities a token establishes. Both are always populated: on an
481
+ * undelegated token they are the same DID, and spelling that out is what
482
+ * keeps the mapping onto asEntity() mechanical.
483
+ *
484
+ * stack.asEntity(session.principalId, { onBehalfOf: session.subjectId })
485
+ *
486
+ * See docs/spec/access-control.md § Delegation: principal and subject.
487
+ */
488
+ export type TokenSession = {
489
+ /** Who authenticated — the DID that proved key possession. Governs authority. */
490
+ principalId: EntityId;
491
+ /** Who the principal acts for. Governs attribution. Equal to principalId unless delegated. */
492
+ subjectId: EntityId;
493
+ };
494
+ export type TokenInfo = TokenSession & {
495
+ id: string;
496
+ label?: string;
497
+ createdAt: Date;
498
+ expiresAt?: Date;
499
+ };
500
+ /**
501
+ * Bearer-token issuance and lookup for server implementations — a
502
+ * standalone interface, not a slot on StackAdapter. createToken() trusts
503
+ * its caller: DID verification happens first, via the challenge-response
504
+ * handshake. Tokens SHOULD be stored outside the portable stack file.
505
+ * See docs/spec/wire-format.md § Authentication.
506
+ *
507
+ * `onBehalfOf` is the delegation binding, and no handshake can establish
508
+ * it — proving key possession proves the principal and nothing about whom
509
+ * it may act for. It is asserted by the owner out of band, which is safe
510
+ * because effective authority is the intersection of both parties' grants.
511
+ */
512
+ export interface StackTokenStore {
513
+ createToken(principalId: EntityId, opts?: {
514
+ onBehalfOf?: EntityId;
515
+ label?: string;
516
+ expiresAt?: Date;
517
+ }): Promise<{
518
+ id: string;
519
+ token: string;
520
+ }>;
521
+ lookupToken(token: string): Promise<TokenSession | null>;
522
+ listTokens(): Promise<TokenInfo[]>;
523
+ revokeToken(id: string): Promise<void>;
524
+ }
275
525
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -4,6 +4,20 @@
4
4
  * This file is the source of truth for all types in the
5
5
  * Stack library. No logic lives here — just shapes.
6
6
  */
7
+ /**
8
+ * Actions that can be granted via a _grant record. The array is the source
9
+ * of truth — GrantAction is derived from it so runtime validation (see
10
+ * Stack.grant()) can't drift from the type.
11
+ */
12
+ export const GRANT_ACTIONS = [
13
+ 'create',
14
+ 'read-own',
15
+ 'read-any',
16
+ 'update-own',
17
+ 'update-any',
18
+ 'delete-own',
19
+ 'delete-any',
20
+ ];
7
21
  /** Reserved system type IDs */
8
22
  export const SYSTEM_TYPES = {
9
23
  ENTITY: '_entity',
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuNH,+BAA+B;AAC/B,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,MAAM;IACX,KAAK,EAAE,QAAQ;IACf,oDAAoD;IACpD,KAAK,EAAE,QAAQ;IACf,0DAA0D;IAC1D,UAAU,EAAE,aAAa;IACzB,8DAA8D;IAC9D,MAAM,EAAE,SAAS;CACT,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsPH;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,QAAQ;IACR,UAAU;IACV,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;CACJ,CAAC;AAyCX,+BAA+B;AAC/B,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,MAAM;IACX,KAAK,EAAE,QAAQ;IACf,oDAAoD;IACpD,KAAK,EAAE,QAAQ;IACf,0DAA0D;IAC1D,UAAU,EAAE,aAAa;IACzB,8DAA8D;IAC9D,MAAM,EAAE,SAAS;CACT,CAAC"}
@@ -20,6 +20,32 @@ export type ValidationError = {
20
20
  * @param errors - Internal: error accumulator for recursive calls
21
21
  */
22
22
  export declare const validateContent: (content: Record<string, unknown>, schema: TypeSchema, prefix?: string, errors?: ValidationError[], depth?: number) => ValidationError[];
23
+ /**
24
+ * Content keys that name JavaScript's object machinery rather than a
25
+ * field. Undeclared content fields are allowed by design, so without this
26
+ * they reach `merged[key] = value` in applyMergePatch — where `__proto__`
27
+ * invokes the prototype setter instead of setting a field.
28
+ *
29
+ * That is not a prototype-pollution gadget today: JSON.stringify writes
30
+ * only own-enumerable properties, so a reassigned prototype never reaches
31
+ * storage, and JSON.parse recreates `__proto__` as an own property rather
32
+ * than invoking the setter, so a stored one round-trips inertly. The
33
+ * defect is that the two write paths disagree — a merge patch to
34
+ * `__proto__` silently vanishes, while the same key through create()
35
+ * stores as an ordinary field — and that a future refactor deepening the
36
+ * shallow merge would turn a quiet inconsistency into a real one.
37
+ *
38
+ * Rejecting is more honest than skipping: a caller who sends one of these
39
+ * finds out, rather than watching a write appear to succeed and do
40
+ * nothing. See docs/spec/data-model.md § Reserved content keys.
41
+ */
42
+ export declare const RESERVED_CONTENT_KEYS: readonly string[];
43
+ /**
44
+ * Top-level only. Nested occurrences survive the JSON round trip as inert
45
+ * own properties (see above), and walking undeclared subtrees to find them
46
+ * would cost a traversal the schema walk doesn't otherwise do.
47
+ */
48
+ export declare const validateReservedKeys: (content: Record<string, unknown>) => ValidationError[];
23
49
  /**
24
50
  * Returns true if the content is valid against the schema.
25
51
  * Use validateContent() directly to get error details.
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA6B,MAAM,YAAY,CAAC;AAQxE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA8EF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,UAAU,EAClB,eAAW,EACX,SAAQ,eAAe,EAAO,EAC9B,cAAS,KACR,eAAe,EAyBjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,UAAU,KAAG,OAChC,CAAC"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA6B,MAAM,YAAY,CAAC;AAmBxE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA6FF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,UAAU,EAClB,eAAW,EACX,SAAQ,eAAe,EAAO,EAC9B,cAAS,KACR,eAAe,EAyBjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAA8C,CAAC;AAElG;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,eAAe,EAIlF,CAAC;AAEN;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,UAAU,KAAG,OAChC,CAAC"}
package/dist/validate.js CHANGED
@@ -8,6 +8,15 @@
8
8
  * Coercion is never performed — types must match exactly.
9
9
  */
10
10
  const MAX_VALIDATION_DEPTH = 32;
11
+ /** fileId format: SHA-256 hex, lowercase — matches blob-adapter-disk's assertFileId(). */
12
+ const FILE_ID_RE = /^[0-9a-f]{64}$/;
13
+ /**
14
+ * ISO 8601 date or date-time shape. The regex pins the shape (bare
15
+ * `Date.parse` accepts engine-dependent non-ISO formats); `Date.parse`
16
+ * still runs afterward as a calendar sanity check. See
17
+ * docs/spec/data-model.md § Types.
18
+ */
19
+ const ISO_8601_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;
11
20
  // -------------------------------------------------------
12
21
  // Internal helpers
13
22
  // -------------------------------------------------------
@@ -16,6 +25,7 @@ const jsTypeForScalar = (kind) => {
16
25
  case 'string':
17
26
  case 'text':
18
27
  case 'record-ref':
28
+ case 'file-ref':
19
29
  return 'string';
20
30
  case 'number':
21
31
  return 'number';
@@ -51,7 +61,9 @@ const validateField = (value, def, path, errors, depth = 0) => {
51
61
  }
52
62
  // Scalar validation
53
63
  if (def.kind === 'date') {
54
- if (typeof value !== 'string' || isNaN(Date.parse(value))) {
64
+ if (typeof value !== 'string' ||
65
+ !ISO_8601_RE.test(value) ||
66
+ isNaN(Date.parse(value))) {
55
67
  errors.push({
56
68
  path,
57
69
  message: `Expected ISO 8601 date string, got ${typeof value}`,
@@ -59,6 +71,15 @@ const validateField = (value, def, path, errors, depth = 0) => {
59
71
  }
60
72
  return;
61
73
  }
74
+ if (def.kind === 'file-ref') {
75
+ if (typeof value !== 'string' || !FILE_ID_RE.test(value)) {
76
+ errors.push({
77
+ path,
78
+ message: 'Expected a 64-character lowercase hex fileId (SHA-256)',
79
+ });
80
+ }
81
+ return;
82
+ }
62
83
  const expected = jsTypeForScalar(def.kind);
63
84
  if (typeof value !== expected) {
64
85
  errors.push({
@@ -99,6 +120,35 @@ export const validateContent = (content, schema, prefix = '', errors = [], depth
99
120
  }
100
121
  return errors;
101
122
  };
123
+ /**
124
+ * Content keys that name JavaScript's object machinery rather than a
125
+ * field. Undeclared content fields are allowed by design, so without this
126
+ * they reach `merged[key] = value` in applyMergePatch — where `__proto__`
127
+ * invokes the prototype setter instead of setting a field.
128
+ *
129
+ * That is not a prototype-pollution gadget today: JSON.stringify writes
130
+ * only own-enumerable properties, so a reassigned prototype never reaches
131
+ * storage, and JSON.parse recreates `__proto__` as an own property rather
132
+ * than invoking the setter, so a stored one round-trips inertly. The
133
+ * defect is that the two write paths disagree — a merge patch to
134
+ * `__proto__` silently vanishes, while the same key through create()
135
+ * stores as an ordinary field — and that a future refactor deepening the
136
+ * shallow merge would turn a quiet inconsistency into a real one.
137
+ *
138
+ * Rejecting is more honest than skipping: a caller who sends one of these
139
+ * finds out, rather than watching a write appear to succeed and do
140
+ * nothing. See docs/spec/data-model.md § Reserved content keys.
141
+ */
142
+ export const RESERVED_CONTENT_KEYS = ['__proto__', 'constructor', 'prototype'];
143
+ /**
144
+ * Top-level only. Nested occurrences survive the JSON round trip as inert
145
+ * own properties (see above), and walking undeclared subtrees to find them
146
+ * would cost a traversal the schema walk doesn't otherwise do.
147
+ */
148
+ export const validateReservedKeys = (content) => RESERVED_CONTENT_KEYS.filter((key) => Object.hasOwn(content, key)).map((key) => ({
149
+ path: key,
150
+ message: `"${key}" is a reserved content key and cannot be used as a field name`,
151
+ }));
102
152
  /**
103
153
  * Returns true if the content is valid against the schema.
104
154
  * Use validateContent() directly to get error details.