@haverstack/core 0.28.0 → 0.30.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 (94) hide show
  1. package/README.md +9 -2
  2. package/dist/access.d.ts +20 -0
  3. package/dist/access.d.ts.map +1 -1
  4. package/dist/access.js +26 -0
  5. package/dist/access.js.map +1 -1
  6. package/dist/adapter-entry.d.ts +1 -1
  7. package/dist/adapter-entry.d.ts.map +1 -1
  8. package/dist/adapter-entry.js +1 -1
  9. package/dist/adapter-entry.js.map +1 -1
  10. package/dist/changes.d.ts +15 -2
  11. package/dist/changes.d.ts.map +1 -1
  12. package/dist/changes.js +62 -6
  13. package/dist/changes.js.map +1 -1
  14. package/dist/combine.d.ts.map +1 -1
  15. package/dist/combine.js +1 -4
  16. package/dist/combine.js.map +1 -1
  17. package/dist/errors.d.ts +173 -0
  18. package/dist/errors.d.ts.map +1 -0
  19. package/dist/errors.js +208 -0
  20. package/dist/errors.js.map +1 -0
  21. package/dist/grants.d.ts +84 -0
  22. package/dist/grants.d.ts.map +1 -0
  23. package/dist/grants.js +157 -0
  24. package/dist/grants.js.map +1 -0
  25. package/dist/id.d.ts +7 -5
  26. package/dist/id.d.ts.map +1 -1
  27. package/dist/id.js +17 -15
  28. package/dist/id.js.map +1 -1
  29. package/dist/identity-bindings.d.ts +13 -0
  30. package/dist/identity-bindings.d.ts.map +1 -0
  31. package/dist/identity-bindings.js +42 -0
  32. package/dist/identity-bindings.js.map +1 -0
  33. package/dist/index.d.ts +7 -4
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +5 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/limits.d.ts +23 -0
  38. package/dist/limits.d.ts.map +1 -0
  39. package/dist/limits.js +35 -0
  40. package/dist/limits.js.map +1 -0
  41. package/dist/merge.d.ts +1 -1
  42. package/dist/merge.js +2 -2
  43. package/dist/merge.js.map +1 -1
  44. package/dist/query-validation.d.ts +80 -0
  45. package/dist/query-validation.d.ts.map +1 -0
  46. package/dist/query-validation.js +223 -0
  47. package/dist/query-validation.js.map +1 -0
  48. package/dist/record-changes.d.ts +89 -0
  49. package/dist/record-changes.d.ts.map +1 -0
  50. package/dist/record-changes.js +201 -0
  51. package/dist/record-changes.js.map +1 -0
  52. package/dist/record-id.d.ts +70 -0
  53. package/dist/record-id.d.ts.map +1 -0
  54. package/dist/record-id.js +127 -0
  55. package/dist/record-id.js.map +1 -0
  56. package/dist/schema.d.ts.map +1 -1
  57. package/dist/schema.js +56 -5
  58. package/dist/schema.js.map +1 -1
  59. package/dist/scoped-stack.d.ts +460 -0
  60. package/dist/scoped-stack.d.ts.map +1 -0
  61. package/dist/scoped-stack.js +1163 -0
  62. package/dist/scoped-stack.js.map +1 -0
  63. package/dist/stack-reads.d.ts +38 -0
  64. package/dist/stack-reads.d.ts.map +1 -0
  65. package/dist/stack-reads.js +79 -0
  66. package/dist/stack-reads.js.map +1 -0
  67. package/dist/stack.d.ts +108 -700
  68. package/dist/stack.d.ts.map +1 -1
  69. package/dist/stack.js +235 -2286
  70. package/dist/stack.js.map +1 -1
  71. package/dist/testing.d.ts +3 -14
  72. package/dist/testing.d.ts.map +1 -1
  73. package/dist/testing.js +30 -42
  74. package/dist/testing.js.map +1 -1
  75. package/dist/types.d.ts +107 -30
  76. package/dist/types.d.ts.map +1 -1
  77. package/dist/types.js +16 -0
  78. package/dist/types.js.map +1 -1
  79. package/dist/validate.d.ts +31 -0
  80. package/dist/validate.d.ts.map +1 -1
  81. package/dist/validate.js +150 -3
  82. package/dist/validate.js.map +1 -1
  83. package/dist/wire-entry.d.ts +3 -3
  84. package/dist/wire-entry.d.ts.map +1 -1
  85. package/dist/wire-entry.js +3 -3
  86. package/dist/wire-entry.js.map +1 -1
  87. package/dist/wire-record.d.ts +16 -1
  88. package/dist/wire-record.d.ts.map +1 -1
  89. package/dist/wire-record.js +57 -1
  90. package/dist/wire-record.js.map +1 -1
  91. package/dist/wire-request.d.ts.map +1 -1
  92. package/dist/wire-request.js +3 -2
  93. package/dist/wire-request.js.map +1 -1
  94. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -14,10 +14,13 @@
14
14
  * Storage adapters are published as separate packages:
15
15
  * @haverstack/adapter-sqlite
16
16
  */
17
- export { Stack, ScopedStack, StackError, StackValidationError, StackMigrationError, StackPermissionError, StackNotFoundError, StackConflictError, StackVersionConflictError, StackQueryError, StackSchemaDriftError, StackPayloadTooLargeError, StackTimeoutError, StackClosedError, StackRelayScopeError, } from './stack.js';
18
- export type { StackErrorCode, StackClient, CreateRecordOptions, BackdatableCreateRecordOptions, StackOptions, GetRecordOptions, DeleteRecordOptions, IfVersionOptions, CollectAttachmentGarbageOptions, CollectAttachmentGarbageResult, } from './stack.js';
19
- export type { RecordId, TypeId, FileId, EntityId, AttachmentContent, StackRecord, RecordVersion, ActorOptions, ChangeKind, ChangeOp, ChangeActor, RecordChange, ChangeFilter, SubscribeOptions, Unsubscribe, ExpectedVersionOptions, SnapshotOptions, StackType, TypeSchema, FieldDef, ScalarFieldDef, ArrayFieldDef, ObjectFieldDef, ScalarFieldKind, Association, TagAssociation, AttachmentAssociation, RelationshipAssociation, RelationshipTarget, RecordTarget, EntityTarget, ExternalTarget, Permission, StackQuery, RecordFilter, RelatedToFilter, RelationshipTargetPattern, QuerySort, NativeSortField, ContentFilterReach, QueryResult, DateRange, Migration, MigrationFn, StackAdapter, StackFeatures, TokenSession, EntityContent, AppContent, GroupContent, GrantAction, GrantContent, ConfigContent, } from './types.js';
20
- export { SYSTEM_TYPES } from './types.js';
17
+ export { Stack } from './stack.js';
18
+ export { ScopedStack } from './scoped-stack.js';
19
+ export type { StackClient, CreateRecordOptions, BackdatableCreateRecordOptions, StackOptions, GetRecordOptions, DeleteRecordOptions, IfVersionOptions, CollectAttachmentGarbageOptions, CollectAttachmentGarbageResult, } from './stack.js';
20
+ export { StackError, StackValidationError, StackMigrationError, StackPermissionError, StackNotFoundError, StackConflictError, StackVersionConflictError, StackQueryError, StackSchemaDriftError, StackPayloadTooLargeError, StackTimeoutError, StackClosedError, StackRelayScopeError, } from './errors.js';
21
+ export type { StackErrorCode } from './errors.js';
22
+ export type { RecordId, TypeId, FileId, EntityId, AttachmentContent, StackRecord, RecordChanges, RecordVersion, ActorOptions, ChangeKind, ChangeOp, ChangeActor, RecordChange, ChangeFilter, SubscribeOptions, Unsubscribe, ExpectedVersionOptions, SnapshotOptions, StackType, TypeSchema, FieldDef, ScalarFieldDef, ArrayFieldDef, ObjectFieldDef, ScalarFieldKind, Association, TagAssociation, AttachmentAssociation, RelationshipAssociation, RelationshipTarget, RecordTarget, EntityTarget, ExternalTarget, Permission, StackQuery, RecordFilter, RelatedToFilter, RelationshipTargetPattern, QuerySort, NativeSortField, ContentFilterReach, QueryResult, DateRange, Migration, MigrationFn, StackAdapter, StackFeatures, TokenSession, EntityContent, AppContent, GroupContent, GrantAction, GrantContent, ConfigContent, } from './types.js';
23
+ export { SYSTEM_TYPES, NATIVE_SORT_FIELDS } from './types.js';
21
24
  export { generateId } from './id.js';
22
25
  export { hashSchema, isCompatible } from './schema.js';
23
26
  export type { SchemaDriftViolation } from './schema.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,KAAK,EACL,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,8BAA8B,EAC9B,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,SAAS,EACT,UAAU,EACV,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,eAAe,EACf,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,UAAU,EACV,UAAU,EACV,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,SAAS,EACT,WAAW,EACX,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAM1C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,8BAA8B,EAC9B,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,YAAY,EACV,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,SAAS,EACT,UAAU,EACV,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,eAAe,EACf,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,UAAU,EACV,UAAU,EACV,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,SAAS,EACT,WAAW,EACX,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAM9D,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -15,8 +15,11 @@
15
15
  * @haverstack/adapter-sqlite
16
16
  */
17
17
  // Core class and client interface
18
- export { Stack, ScopedStack, StackError, StackValidationError, StackMigrationError, StackPermissionError, StackNotFoundError, StackConflictError, StackVersionConflictError, StackQueryError, StackSchemaDriftError, StackPayloadTooLargeError, StackTimeoutError, StackClosedError, StackRelayScopeError, } from './stack.js';
19
- export { SYSTEM_TYPES } from './types.js';
18
+ export { Stack } from './stack.js';
19
+ export { ScopedStack } from './scoped-stack.js';
20
+ // The error taxonomy
21
+ export { StackError, StackValidationError, StackMigrationError, StackPermissionError, StackNotFoundError, StackConflictError, StackVersionConflictError, StackQueryError, StackSchemaDriftError, StackPayloadTooLargeError, StackTimeoutError, StackClosedError, StackRelayScopeError, } from './errors.js';
22
+ export { SYSTEM_TYPES, NATIVE_SORT_FIELDS } from './types.js';
20
23
  // Utilities
21
24
  // No in-repo caller: client-minted IDs are the default per spec, and an app
22
25
  // needing the ID before the write round-trips has to mint one itself.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,kCAAkC;AAClC,OAAO,EACL,KAAK,EACL,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAuEpB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,YAAY;AAEZ,4EAA4E;AAC5E,sEAAsE;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,kCAAkC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAahD,qBAAqB;AACrB,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AA6DrB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE9D,YAAY;AAEZ,4EAA4E;AAC5E,sEAAsE;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Declared-ceiling pre-checks
3
+ * -------------------------------------------------------
4
+ * Client-side guards against an adapter's declared `limits`. Local
5
+ * adapters declare `null` and skip them entirely; only a server names a
6
+ * ceiling, and its own request-size limit stays authoritative — these just
7
+ * spare an app the round trip and give it a typed failure instead of a 413
8
+ * it has to interpret.
9
+ *
10
+ * See docs/spec/wire-format.md § Request size limits.
11
+ */
12
+ /** Shared by Stack.putAttachment() and ScopedStack.putAttachment(). */
13
+ export declare function assertAttachmentSize(byteLength: number, attachmentBytes: number | null): void;
14
+ /**
15
+ * The content half of the same pre-check, on Stack.create() and
16
+ * Stack.mutate(). Local adapters declare `limits.contentBytes: null` and skip
17
+ * the serialization entirely; only a server declares a ceiling, and its
18
+ * own request-size limit stays authoritative — this just spares an app the
19
+ * round trip and gives it a typed failure instead of a 413 it has to
20
+ * interpret. See docs/spec/wire-format.md § Request size limits.
21
+ */
22
+ export declare function assertContentSize(content: Record<string, unknown>, contentBytes: number | null, what: 'Content' | 'Patch'): void;
23
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,uEAAuE;AACvE,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAM7F;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,IAAI,EAAE,SAAS,GAAG,OAAO,GACxB,IAAI,CAQN"}
package/dist/limits.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Declared-ceiling pre-checks
3
+ * -------------------------------------------------------
4
+ * Client-side guards against an adapter's declared `limits`. Local
5
+ * adapters declare `null` and skip them entirely; only a server names a
6
+ * ceiling, and its own request-size limit stays authoritative — these just
7
+ * spare an app the round trip and give it a typed failure instead of a 413
8
+ * it has to interpret.
9
+ *
10
+ * See docs/spec/wire-format.md § Request size limits.
11
+ */
12
+ import { StackPayloadTooLargeError } from './errors.js';
13
+ /** Shared by Stack.putAttachment() and ScopedStack.putAttachment(). */
14
+ export function assertAttachmentSize(byteLength, attachmentBytes) {
15
+ if (attachmentBytes !== null && byteLength > attachmentBytes) {
16
+ throw new StackPayloadTooLargeError(`Attachment (${byteLength} bytes) exceeds the ${attachmentBytes}-byte limit.`);
17
+ }
18
+ }
19
+ /**
20
+ * The content half of the same pre-check, on Stack.create() and
21
+ * Stack.mutate(). Local adapters declare `limits.contentBytes: null` and skip
22
+ * the serialization entirely; only a server declares a ceiling, and its
23
+ * own request-size limit stays authoritative — this just spares an app the
24
+ * round trip and gives it a typed failure instead of a 413 it has to
25
+ * interpret. See docs/spec/wire-format.md § Request size limits.
26
+ */
27
+ export function assertContentSize(content, contentBytes, what) {
28
+ if (contentBytes === null)
29
+ return;
30
+ const byteLength = new TextEncoder().encode(JSON.stringify(content)).length;
31
+ if (byteLength > contentBytes) {
32
+ throw new StackPayloadTooLargeError(`${what} (${byteLength} bytes) exceeds the ${contentBytes}-byte limit.`);
33
+ }
34
+ }
35
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAExD,uEAAuE;AACvE,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,eAA8B;IACrF,IAAI,eAAe,KAAK,IAAI,IAAI,UAAU,GAAG,eAAe,EAAE,CAAC;QAC7D,MAAM,IAAI,yBAAyB,CACjC,eAAe,UAAU,uBAAuB,eAAe,cAAc,CAC9E,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAgC,EAChC,YAA2B,EAC3B,IAAyB;IAEzB,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO;IAClC,MAAM,UAAU,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5E,IAAI,UAAU,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,IAAI,yBAAyB,CACjC,GAAG,IAAI,KAAK,UAAU,uBAAuB,YAAY,cAAc,CACxE,CAAC;IACJ,CAAC;AACH,CAAC"}
package/dist/merge.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * -------------------------------------------------------
4
4
  * Shallow merge-patch semantics for record content: a field set to `null`
5
5
  * removes it, any other value replaces it, and omitted fields are retained.
6
- * Shared by Stack.update() (for client-side validation) and every
6
+ * Shared by Stack.mutate() (for client-side validation) and every
7
7
  * StackRecordAdapter's patchContent() implementation, so a patch produces
8
8
  * the same merged content everywhere it's applied.
9
9
  */
package/dist/merge.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * -------------------------------------------------------
4
4
  * Shallow merge-patch semantics for record content: a field set to `null`
5
5
  * removes it, any other value replaces it, and omitted fields are retained.
6
- * Shared by Stack.update() (for client-side validation) and every
6
+ * Shared by Stack.mutate() (for client-side validation) and every
7
7
  * StackRecordAdapter's patchContent() implementation, so a patch produces
8
8
  * the same merged content everywhere it's applied.
9
9
  */
@@ -22,7 +22,7 @@ export function applyMergePatch(content, patch) {
22
22
  // the same key is read back, so both write paths agree. The spread
23
23
  // above is already safe (it copies data properties directly).
24
24
  //
25
- // Stack.create()/update() reject the reserved keys outright (see
25
+ // Stack.create()/Stack.mutate() reject the reserved keys outright (see
26
26
  // validateReservedKeys); this is the backstop for adapters that call
27
27
  // this directly, and for the day someone deepens the merge.
28
28
  Object.defineProperty(merged, key, {
package/dist/merge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"merge.js","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,UAAU,eAAe,CAC7B,OAAgC,EAChC,KAAqC;IAErC,MAAM,MAAM,GAA4B,EAAE,GAAG,OAAO,EAAE,CAAC;IACvD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,qEAAqE;YACrE,oEAAoE;YACpE,kEAAkE;YAClE,mEAAmE;YACnE,qEAAqE;YACrE,mEAAmE;YACnE,8DAA8D;YAC9D,EAAE;YACF,iEAAiE;YACjE,qEAAqE;YACrE,4DAA4D;YAC5D,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjC,KAAK;gBACL,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;gBAChB,YAAY,EAAE,IAAI;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,UAAU,eAAe,CAC7B,OAAgC,EAChC,KAAqC;IAErC,MAAM,MAAM,GAA4B,EAAE,GAAG,OAAO,EAAE,CAAC;IACvD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,qEAAqE;YACrE,oEAAoE;YACpE,kEAAkE;YAClE,mEAAmE;YACnE,qEAAqE;YACrE,mEAAmE;YACnE,8DAA8D;YAC9D,EAAE;YACF,uEAAuE;YACvE,qEAAqE;YACrE,4DAA4D;YAC5D,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjC,KAAK;gBACL,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;gBAChB,YAAY,EAAE,IAAI;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Query and association validation
3
+ * -------------------------------------------------------
4
+ * The sanitizers a query passes through before any adapter sees it, plus
5
+ * the relationship-target rules an association is held to. Both sides of
6
+ * the same job: a discriminated union and a `QuerySort` field are compile-
7
+ * time promises, and a server mapping a request body onto either supplies
8
+ * raw JSON that no compiler has seen.
9
+ *
10
+ * These live in the invariant layer for the reason validation and
11
+ * `_config` protection do — so no adapter can forget one. `assertQueryCapabilities`
12
+ * and its neighbours are shared verbatim with `adapter-api`, which asks the
13
+ * same questions of a request arriving over the wire.
14
+ * See docs/spec/data-model.md § Capability-gated filters.
15
+ */
16
+ import type { ValidationError } from './validate.js';
17
+ import type { Association, QuerySort, RecordFilter, StackFeatures } from './types.js';
18
+ /**
19
+ * Fail loud rather than silently widen: a filter the adapter can't honor
20
+ * would otherwise be dropped, returning an unfiltered superset presented
21
+ * as the filtered result. Shared by Stack.query() and
22
+ * APIAdapter.queryRecords(). See docs/spec/data-model.md
23
+ * § Capability-gated filters.
24
+ */
25
+ export declare function assertQueryCapabilities(filter: RecordFilter | undefined, capabilities: Pick<StackFeatures, 'filter'>): void;
26
+ /**
27
+ * Whether a single-segment content filter can be pushed down to the
28
+ * adapter. Callers that read a content field pair this with an in-memory
29
+ * predicate over the wider result, so the query stays correct against an
30
+ * adapter that reaches no content at all — the rung comparison lives here
31
+ * rather than at each of them.
32
+ */
33
+ export declare function filtersContent(features: Pick<StackFeatures, 'filter'>): boolean;
34
+ /**
35
+ * Split a content filter key into path segments. Write-time validation
36
+ * keeps stored field names free of the path metacharacters, but a filter
37
+ * arrives from a request body and has made no such promise, so the same
38
+ * rule is enforced here. See docs/spec/data-model.md § Content field names.
39
+ */
40
+ export declare function parseContentFilterKey(key: string): string[];
41
+ /**
42
+ * Reject a sort whose field or direction is outside the closed set the
43
+ * types promise. `QuerySort` is typed `'asc' | 'desc'`, but a type is not a
44
+ * runtime guard: a server mapping `?direction=` onto a query, or a
45
+ * delegated app calling query(), supplies a raw string. A SQLite record
46
+ * adapter interpolates the direction straight into `ORDER BY`, so an
47
+ * unvalidated value there is a SQL-injection sink reachable from every
48
+ * untrusted caller. Validating in the invariant layer — the same reason
49
+ * emission and _config protection live here — means no adapter can forget
50
+ * it. See docs/spec/data-model.md § Sorting and pagination.
51
+ */
52
+ export declare function assertValidSort(sort: QuerySort | undefined): void;
53
+ /**
54
+ * Fail loud rather than silently reorder: an adapter that can't honor the
55
+ * requested sort would otherwise answer in some other order, which a
56
+ * caller paging a bounded window has no way to notice. The companion to
57
+ * assertQueryCapabilities(), split from it because a sort is not a filter
58
+ * — see docs/spec/data-model.md § Capability-gated filters.
59
+ */
60
+ export declare function assertSortCapability(sort: QuerySort | undefined, capabilities: Pick<StackFeatures, 'sort'>): void;
61
+ /**
62
+ * Reject a relationship target outside the closed set the types promise.
63
+ * A discriminated union is not a runtime guard — a server mapping a
64
+ * request body onto an association supplies raw JSON — and an
65
+ * unrecognized scope would otherwise be stored under the one arm that
66
+ * names a Record in this stack. See docs/spec/data-model.md
67
+ * § Relationship targets.
68
+ */
69
+ export declare function validateAssociation(association: Association, path?: string): ValidationError[];
70
+ /** validateAssociation() over a create's `associations` array. */
71
+ export declare function validateAssociations(associations: Association[] | undefined, path?: string): ValidationError[];
72
+ /**
73
+ * Reject a relationship filter that names neither a label nor a target,
74
+ * or whose target is malformed. `RelatedToFilter` promises one half is
75
+ * always present; without the runtime check a filter decoded from a
76
+ * request could arrive empty and match every record carrying any
77
+ * relationship. See docs/spec/data-model.md § Filter.
78
+ */
79
+ export declare function assertValidRelatedTo(relatedTo: RecordFilter['relatedTo']): void;
80
+ //# sourceMappingURL=query-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-validation.d.ts","sourceRoot":"","sources":["../src/query-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,YAAY,EAGZ,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,YAAY,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,GAC1C,IAAI,CA6BN;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,GAAG,OAAO,CAE/E;AAKD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwB3D;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CA2BjE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,GAAG,SAAS,EAC3B,YAAY,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,GACxC,IAAI,CAiBN;AA0CD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,WAAW,EACxB,IAAI,SAAgB,GACnB,eAAe,EAAE,CAGnB;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,EACvC,IAAI,SAAiB,GACpB,eAAe,EAAE,CAEnB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC,GAAG,IAAI,CAY/E"}
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Query and association validation
3
+ * -------------------------------------------------------
4
+ * The sanitizers a query passes through before any adapter sees it, plus
5
+ * the relationship-target rules an association is held to. Both sides of
6
+ * the same job: a discriminated union and a `QuerySort` field are compile-
7
+ * time promises, and a server mapping a request body onto either supplies
8
+ * raw JSON that no compiler has seen.
9
+ *
10
+ * These live in the invariant layer for the reason validation and
11
+ * `_config` protection do — so no adapter can forget one. `assertQueryCapabilities`
12
+ * and its neighbours are shared verbatim with `adapter-api`, which asks the
13
+ * same questions of a request arriving over the wire.
14
+ * See docs/spec/data-model.md § Capability-gated filters.
15
+ */
16
+ import { StackQueryError } from './errors.js';
17
+ import { CONTENT_SEGMENT_METACHARACTERS, SEGMENT_METACHARACTER_RE } from './validate.js';
18
+ import { NATIVE_SORT_FIELDS } from './types.js';
19
+ /**
20
+ * Fail loud rather than silently widen: a filter the adapter can't honor
21
+ * would otherwise be dropped, returning an unfiltered superset presented
22
+ * as the filtered result. Shared by Stack.query() and
23
+ * APIAdapter.queryRecords(). See docs/spec/data-model.md
24
+ * § Capability-gated filters.
25
+ */
26
+ export function assertQueryCapabilities(filter, capabilities) {
27
+ const { content: reach, contentPresent, search } = capabilities.filter;
28
+ if (filter?.search && !search) {
29
+ throw new StackQueryError('Query uses filter.search, but this adapter does not declare the filter.search capability.');
30
+ }
31
+ const present = filter?.contentPresent?.length ? filter.contentPresent : undefined;
32
+ if (!filter?.content && !present)
33
+ return;
34
+ if (reach === 'none') {
35
+ throw new StackQueryError(`Query uses ${present && !filter?.content ? 'filter.contentPresent' : 'filter.content'}, ` +
36
+ 'but this adapter declares filter.content: "none".');
37
+ }
38
+ if (present && !contentPresent) {
39
+ throw new StackQueryError('Query uses filter.contentPresent, but this adapter does not declare the ' +
40
+ 'filter.contentPresent capability.');
41
+ }
42
+ for (const key of [...Object.keys(filter?.content ?? {}), ...(present ?? [])]) {
43
+ if (parseContentFilterKey(key).length > 1 && reach !== 'path') {
44
+ throw new StackQueryError(`Query uses the nested content path "${key}", but this adapter declares ` +
45
+ `filter.content: "${reach}".`);
46
+ }
47
+ }
48
+ }
49
+ /**
50
+ * Whether a single-segment content filter can be pushed down to the
51
+ * adapter. Callers that read a content field pair this with an in-memory
52
+ * predicate over the wider result, so the query stays correct against an
53
+ * adapter that reaches no content at all — the rung comparison lives here
54
+ * rather than at each of them.
55
+ */
56
+ export function filtersContent(features) {
57
+ return features.filter.content !== 'none';
58
+ }
59
+ /** The longest path both SQLite engines can execute — see the spec link below. */
60
+ const MAX_CONTENT_PATH_SEGMENTS = 32;
61
+ /**
62
+ * Split a content filter key into path segments. Write-time validation
63
+ * keeps stored field names free of the path metacharacters, but a filter
64
+ * arrives from a request body and has made no such promise, so the same
65
+ * rule is enforced here. See docs/spec/data-model.md § Content field names.
66
+ */
67
+ export function parseContentFilterKey(key) {
68
+ const segments = key.split('.');
69
+ // A SQLite adapter spends two json_each joins per segment against a
70
+ // 64-table join limit, so a path past the cap is one the engine could
71
+ // not execute. See docs/spec/data-model.md § Nested content paths.
72
+ if (segments.length > MAX_CONTENT_PATH_SEGMENTS) {
73
+ throw new StackQueryError(`Invalid content filter path "${key}": at most ${MAX_CONTENT_PATH_SEGMENTS} segments.`);
74
+ }
75
+ for (const segment of segments) {
76
+ if (segment === '') {
77
+ throw new StackQueryError(`Invalid content filter path "${key}": a path segment cannot be empty.`);
78
+ }
79
+ if (SEGMENT_METACHARACTER_RE.test(segment)) {
80
+ throw new StackQueryError(`Invalid content filter path "${key}": a segment cannot contain any of ` +
81
+ `${CONTENT_SEGMENT_METACHARACTERS.join(' ')}.`);
82
+ }
83
+ }
84
+ return segments;
85
+ }
86
+ /**
87
+ * The only sort fields any adapter maps; anything else is a caller error.
88
+ * Narrowed against the one array NativeSortField is derived from, so this
89
+ * gate — which every query passes through before an adapter sees it —
90
+ * can't be the copy that a fourth native column is missing from.
91
+ */
92
+ const VALID_SORT_FIELDS = new Set(NATIVE_SORT_FIELDS);
93
+ /** The only two sort directions; see assertValidSort. */
94
+ const VALID_SORT_DIRECTIONS = new Set(['asc', 'desc']);
95
+ /**
96
+ * Reject a sort whose field or direction is outside the closed set the
97
+ * types promise. `QuerySort` is typed `'asc' | 'desc'`, but a type is not a
98
+ * runtime guard: a server mapping `?direction=` onto a query, or a
99
+ * delegated app calling query(), supplies a raw string. A SQLite record
100
+ * adapter interpolates the direction straight into `ORDER BY`, so an
101
+ * unvalidated value there is a SQL-injection sink reachable from every
102
+ * untrusted caller. Validating in the invariant layer — the same reason
103
+ * emission and _config protection live here — means no adapter can forget
104
+ * it. See docs/spec/data-model.md § Sorting and pagination.
105
+ */
106
+ export function assertValidSort(sort) {
107
+ if (!sort)
108
+ return;
109
+ if (sort.field !== undefined && sort.contentField !== undefined) {
110
+ throw new StackQueryError('A sort names either a native field or a content field, never both.');
111
+ }
112
+ if (sort.field !== undefined && !VALID_SORT_FIELDS.has(sort.field)) {
113
+ throw new StackQueryError(`Invalid sort field "${sort.field}": expected one of ${NATIVE_SORT_FIELDS.join(', ')}.`);
114
+ }
115
+ if (sort.contentField !== undefined) {
116
+ // Parsed by the same rule a filter key is, so a name a filter could
117
+ // never address is not one a sort can either — then held to one
118
+ // segment, because a value inside an array or object has no single
119
+ // position to order its record by (docs/spec/data-model.md
120
+ // § Sorting by a content field).
121
+ if (parseContentFilterKey(sort.contentField).length > 1) {
122
+ throw new StackQueryError(`Invalid sort content field "${sort.contentField}": sorting reaches top-level fields only.`);
123
+ }
124
+ }
125
+ if (sort.direction !== undefined && !VALID_SORT_DIRECTIONS.has(sort.direction)) {
126
+ throw new StackQueryError(`Invalid sort direction "${sort.direction}": expected "asc" or "desc".`);
127
+ }
128
+ }
129
+ /**
130
+ * Fail loud rather than silently reorder: an adapter that can't honor the
131
+ * requested sort would otherwise answer in some other order, which a
132
+ * caller paging a bounded window has no way to notice. The companion to
133
+ * assertQueryCapabilities(), split from it because a sort is not a filter
134
+ * — see docs/spec/data-model.md § Capability-gated filters.
135
+ */
136
+ export function assertSortCapability(sort, capabilities) {
137
+ if (!sort)
138
+ return;
139
+ if (sort.contentField !== undefined) {
140
+ if (!capabilities.sort.contentField) {
141
+ throw new StackQueryError('Query uses sort.contentField, but this adapter does not declare the sort.contentField ' +
142
+ 'capability.');
143
+ }
144
+ return;
145
+ }
146
+ const field = sort.field ?? 'createdAt';
147
+ if (!capabilities.sort.fields.includes(field)) {
148
+ throw new StackQueryError(`Query sorts by "${field}", which this adapter does not declare in sort.fields.`);
149
+ }
150
+ }
151
+ /** The identifier spaces a relationship target may name. */
152
+ const TARGET_SCOPES = new Set(['record', 'entity', 'external']);
153
+ /**
154
+ * Collect what makes a relationship target malformed. Absence is
155
+ * meaningful on `stackUrl` and an external `id` — this stack, and the
156
+ * whole namespace — so every part that names something must be non-empty:
157
+ * an empty string stores and matches as though it were absent.
158
+ * See docs/spec/data-model.md § Relationship targets.
159
+ */
160
+ function targetErrors(target, path, opts = {}) {
161
+ const fail = (message) => [{ path, message }];
162
+ if (!target || typeof target !== 'object')
163
+ return fail('A relationship target must be an object.');
164
+ if (!TARGET_SCOPES.has(target.scope)) {
165
+ return fail(`Unknown relationship target scope "${target.scope}": expected "record", "entity" or "external".`);
166
+ }
167
+ if (target.scope === 'record') {
168
+ if (!target.recordId)
169
+ return fail('A record target requires a non-empty recordId.');
170
+ if (target.stackUrl !== undefined && !target.stackUrl) {
171
+ return fail("A record target's stackUrl must be non-empty; omit it to name this stack.");
172
+ }
173
+ return [];
174
+ }
175
+ if (target.scope === 'entity') {
176
+ return target.entityId ? [] : fail('An entity target requires a non-empty entityId.');
177
+ }
178
+ if (!target.ns)
179
+ return fail('An external target requires a non-empty ns.');
180
+ if (target.id === undefined) {
181
+ return opts.externalIdOptional ? [] : fail('An external target requires an id.');
182
+ }
183
+ return target.id ? [] : fail("An external target's id must be non-empty when present.");
184
+ }
185
+ /**
186
+ * Reject a relationship target outside the closed set the types promise.
187
+ * A discriminated union is not a runtime guard — a server mapping a
188
+ * request body onto an association supplies raw JSON — and an
189
+ * unrecognized scope would otherwise be stored under the one arm that
190
+ * names a Record in this stack. See docs/spec/data-model.md
191
+ * § Relationship targets.
192
+ */
193
+ export function validateAssociation(association, path = 'association') {
194
+ if (association?.kind !== 'relationship')
195
+ return [];
196
+ return targetErrors(association.target, `${path}.target`);
197
+ }
198
+ /** validateAssociation() over a create's `associations` array. */
199
+ export function validateAssociations(associations, path = 'associations') {
200
+ return (associations ?? []).flatMap((a, i) => validateAssociation(a, `${path}[${i}]`));
201
+ }
202
+ /**
203
+ * Reject a relationship filter that names neither a label nor a target,
204
+ * or whose target is malformed. `RelatedToFilter` promises one half is
205
+ * always present; without the runtime check a filter decoded from a
206
+ * request could arrive empty and match every record carrying any
207
+ * relationship. See docs/spec/data-model.md § Filter.
208
+ */
209
+ export function assertValidRelatedTo(relatedTo) {
210
+ if (!relatedTo)
211
+ return;
212
+ if (relatedTo.label === undefined && relatedTo.target === undefined) {
213
+ throw new StackQueryError('filter.relatedTo must name a label, a target, or both — "any relationship at all" is not a filter.');
214
+ }
215
+ if (relatedTo.target === undefined)
216
+ return;
217
+ const errors = targetErrors(relatedTo.target, 'filter.relatedTo.target', {
218
+ externalIdOptional: true,
219
+ });
220
+ if (errors.length > 0)
221
+ throw new StackQueryError(errors[0].message);
222
+ }
223
+ //# sourceMappingURL=query-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-validation.js","sourceRoot":"","sources":["../src/query-validation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * What a write moved, and how a Record is presented
3
+ * -------------------------------------------------------
4
+ * Two jobs that share one question — what does this Record actually say?
5
+ *
6
+ * The equality helpers answer it against a proposed write, so the no-op
7
+ * decision and a change event's `ops` are the same comparison and can never
8
+ * disagree. The projections answer it against a reader: a soft-deleted
9
+ * Record is its tombstone, and a snapshot's `permissions` are owner-only
10
+ * audit data.
11
+ *
12
+ * See docs/spec/events.md § The event shape and docs/spec/versioning.md
13
+ * § The tombstone is literal.
14
+ */
15
+ import type { Association, ChangeOp, EntityId, Permission, RecordChanges, RecordVersion, RelationshipTarget, StackRecord } from './types.js';
16
+ /**
17
+ * Matches the SQLite adapter's association primary key (kind, label,
18
+ * file_id, related_scope, related_id, related_ns, related_stack).
19
+ */
20
+ export declare function associationEqual(a: Association, b: Association): boolean;
21
+ /** Structural equality per target arm — what dissociate() matches on. */
22
+ export declare function targetEqual(a: RelationshipTarget, b: RelationshipTarget): boolean;
23
+ export declare function permissionEqual(a: Permission, b: Permission): boolean;
24
+ /**
25
+ * A change set has to name at least one aspect. Refused rather than read
26
+ * as a no-op: it addresses nothing, so there is nothing it could have
27
+ * failed to satisfy, and every way of producing one is a caller bug —
28
+ * typically a conditional that built an empty object.
29
+ * See docs/spec/data-model.md § Mutations.
30
+ */
31
+ export declare function assertNonEmptyChangeSet(changes: RecordChanges): void;
32
+ /**
33
+ * Which aspects a change set actually moves, against the record as it
34
+ * stands — the no-op decision and the change event's `ops` are the same
35
+ * comparison, so they can never disagree. A key naming the value a record
36
+ * already holds contributes nothing.
37
+ *
38
+ * `merged` is the content the patch produces, computed once by the
39
+ * caller that had to validate it anyway.
40
+ * See docs/spec/events.md § The event shape.
41
+ */
42
+ export declare function changeSetOps(existing: StackRecord, changes: RecordChanges, merged: Record<string, unknown> | undefined): ChangeOp[];
43
+ /**
44
+ * The change set narrowed to the aspects that actually moved. An adapter
45
+ * is handed this rather than what the caller wrote, so restating an aspect
46
+ * cannot rewrite it: `unlisted: true` on an already-unlisted record would
47
+ * otherwise drag `unlistedAt` forward, moving the record's publish moment
48
+ * with no op reporting it. It also keeps the adapter contract honest —
49
+ * every key an adapter receives is one it must write.
50
+ */
51
+ export declare function effectiveChanges(changes: RecordChanges, ops: ChangeOp[]): RecordChanges;
52
+ /**
53
+ * Whether a merge patch produced the content the record already held.
54
+ * Compared by serialization: content is JSON by construction — it round
55
+ * trips through storage that way — and a patch preserves key order for
56
+ * every field it does not name, so the encoding of an unchanged record is
57
+ * stable. A reordering patch that changes nothing else is the one case
58
+ * this reports as a change, which costs an empty version rather than a
59
+ * wrong answer.
60
+ */
61
+ export declare function contentEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean;
62
+ export declare function permissionsEqual(a: Permission[], b: Permission[]): boolean;
63
+ /**
64
+ * Ensures `creator` carries an `admin` relationship association, adding one
65
+ * if it's not already present. Used to bootstrap a `_group` record's first
66
+ * admin at create time.
67
+ */
68
+ export declare function stampGroupAdmin(associations: Association[] | undefined, creator: EntityId): Association[];
69
+ /**
70
+ * Whether a Record is a `_group`, in any of its type versions — the family
71
+ * whose roster rules apply.
72
+ */
73
+ export declare function isGroupRecord(record: StackRecord): boolean;
74
+ /**
75
+ * Drops a snapshot's `permissions` — owner-only audit data, never served
76
+ * to a non-owner history reader. `entityId` (change attribution) stays.
77
+ * See docs/spec/versioning.md § History access.
78
+ */
79
+ export declare function stripVersionPermissions(version: RecordVersion): RecordVersion;
80
+ /**
81
+ * The tombstone a soft-deleted Record is presented as. `permissions` is
82
+ * retained because it decides whether the caller may undelete; history is
83
+ * exempt, which is why getVersions() still serves the content this
84
+ * withholds. See docs/spec/versioning.md § The tombstone is literal.
85
+ */
86
+ export declare function tombstoneOf(record: StackRecord): StackRecord;
87
+ /** A soft-deleted Record presented as its tombstone; anything else untouched. */
88
+ export declare const presentDeleted: (record: StackRecord) => StackRecord;
89
+ //# sourceMappingURL=record-changes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-changes.d.ts","sourceRoot":"","sources":["../src/record-changes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,OAAO,CAOxE;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAQjF;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAUrE;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAQpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC1C,QAAQ,EAAE,CAyBZ;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,aAAa,CAUvF;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAE5F;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,OAAO,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,EACvC,OAAO,EAAE,QAAQ,GAChB,WAAW,EAAE,CAcf;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAE1D;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa,CAI7E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAY5D;AAED,iFAAiF;AACjF,eAAO,MAAM,cAAc,GAAI,QAAQ,WAAW,KAAG,WACJ,CAAC"}