@haverstack/core 0.41.0 → 0.43.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.
- package/README.md +124 -64
- package/dist/access.d.ts +43 -47
- package/dist/access.d.ts.map +1 -1
- package/dist/access.js +66 -101
- package/dist/access.js.map +1 -1
- package/dist/{combine.d.ts → adapter/combine.d.ts} +8 -1
- package/dist/adapter/combine.d.ts.map +1 -0
- package/dist/{combine.js → adapter/combine.js} +18 -3
- package/dist/adapter/combine.js.map +1 -0
- package/dist/adapter/index.d.ts +19 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +16 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/adapter/reach.d.ts +14 -0
- package/dist/adapter/reach.d.ts.map +1 -0
- package/dist/adapter/reach.js +31 -0
- package/dist/adapter/reach.js.map +1 -0
- package/dist/{sort.d.ts → adapter/sort.d.ts} +20 -20
- package/dist/adapter/sort.d.ts.map +1 -0
- package/dist/{sort.js → adapter/sort.js} +37 -27
- package/dist/adapter/sort.js.map +1 -0
- package/dist/associations/identity.d.ts +69 -0
- package/dist/associations/identity.d.ts.map +1 -0
- package/dist/associations/identity.js +174 -0
- package/dist/associations/identity.js.map +1 -0
- package/dist/associations/validation.d.ts +83 -0
- package/dist/associations/validation.d.ts.map +1 -0
- package/dist/associations/validation.js +312 -0
- package/dist/associations/validation.js.map +1 -0
- package/dist/attachment-records.d.ts +48 -0
- package/dist/attachment-records.d.ts.map +1 -0
- package/dist/attachment-records.js +52 -0
- package/dist/attachment-records.js.map +1 -0
- package/dist/collection.d.ts +178 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +397 -0
- package/dist/collection.js.map +1 -0
- package/dist/content-match.d.ts +25 -0
- package/dist/content-match.d.ts.map +1 -0
- package/dist/content-match.js +51 -0
- package/dist/content-match.js.map +1 -0
- package/dist/content-translation.d.ts +42 -0
- package/dist/content-translation.d.ts.map +1 -0
- package/dist/content-translation.js +108 -0
- package/dist/content-translation.js.map +1 -0
- package/dist/did/did.d.ts.map +1 -0
- package/dist/{did.js → did/did.js} +3 -4
- package/dist/did/did.js.map +1 -0
- package/dist/{did-entry.d.ts → did/index.d.ts} +1 -1
- package/dist/did/index.d.ts.map +1 -0
- package/dist/{did-entry.js → did/index.js} +3 -3
- package/dist/did/index.js.map +1 -0
- package/dist/errors.d.ts +54 -66
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +58 -62
- package/dist/errors.js.map +1 -1
- package/dist/grants.d.ts +20 -44
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +69 -151
- package/dist/grants.js.map +1 -1
- package/dist/id.d.ts +13 -31
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +13 -31
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +13 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +3 -6
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +6 -10
- package/dist/install.js.map +1 -1
- package/dist/lazy.d.ts +13 -0
- package/dist/lazy.d.ts.map +1 -0
- package/dist/lazy.js +27 -0
- package/dist/lazy.js.map +1 -0
- package/dist/merge.d.ts +1 -1
- package/dist/merge.d.ts.map +1 -1
- package/dist/merge.js +6 -12
- package/dist/merge.js.map +1 -1
- package/dist/migration-steps.d.ts +102 -0
- package/dist/migration-steps.d.ts.map +1 -0
- package/dist/migration-steps.js +293 -0
- package/dist/migration-steps.js.map +1 -0
- package/dist/query-validation.d.ts +68 -121
- package/dist/query-validation.d.ts.map +1 -1
- package/dist/query-validation.js +214 -380
- package/dist/query-validation.js.map +1 -1
- package/dist/record-changes.d.ts +11 -150
- package/dist/record-changes.d.ts.map +1 -1
- package/dist/record-changes.js +14 -305
- package/dist/record-changes.js.map +1 -1
- package/dist/schema.d.ts +18 -7
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +59 -30
- package/dist/schema.js.map +1 -1
- package/dist/scoped-stack/authority-snapshot.d.ts +37 -0
- package/dist/scoped-stack/authority-snapshot.d.ts.map +1 -0
- package/dist/scoped-stack/authority-snapshot.js +49 -0
- package/dist/scoped-stack/authority-snapshot.js.map +1 -0
- package/dist/scoped-stack/authority.d.ts +169 -0
- package/dist/scoped-stack/authority.d.ts.map +1 -0
- package/dist/scoped-stack/authority.js +408 -0
- package/dist/scoped-stack/authority.js.map +1 -0
- package/dist/scoped-stack/family-policy.d.ts +59 -0
- package/dist/scoped-stack/family-policy.d.ts.map +1 -0
- package/dist/scoped-stack/family-policy.js +68 -0
- package/dist/scoped-stack/family-policy.js.map +1 -0
- package/dist/scoped-stack/scoped-stack.d.ts +318 -0
- package/dist/scoped-stack/scoped-stack.d.ts.map +1 -0
- package/dist/scoped-stack/scoped-stack.js +695 -0
- package/dist/scoped-stack/scoped-stack.js.map +1 -0
- package/dist/scoped-stack/subscription.d.ts +36 -0
- package/dist/scoped-stack/subscription.d.ts.map +1 -0
- package/dist/scoped-stack/subscription.js +69 -0
- package/dist/scoped-stack/subscription.js.map +1 -0
- package/dist/stack/arguments.d.ts +27 -0
- package/dist/stack/arguments.d.ts.map +1 -0
- package/dist/stack/arguments.js +53 -0
- package/dist/stack/arguments.js.map +1 -0
- package/dist/stack/attachments.d.ts +24 -0
- package/dist/stack/attachments.d.ts.map +1 -0
- package/dist/stack/attachments.js +113 -0
- package/dist/stack/attachments.js.map +1 -0
- package/dist/{changes.d.ts → stack/changes.d.ts} +30 -72
- package/dist/stack/changes.d.ts.map +1 -0
- package/dist/{changes.js → stack/changes.js} +40 -74
- package/dist/stack/changes.js.map +1 -0
- package/dist/stack/client.d.ts +247 -0
- package/dist/stack/client.d.ts.map +1 -0
- package/dist/stack/client.js +9 -0
- package/dist/stack/client.js.map +1 -0
- package/dist/stack/identity-bindings.d.ts.map +1 -0
- package/dist/{identity-bindings.js → stack/identity-bindings.js} +5 -14
- package/dist/stack/identity-bindings.js.map +1 -0
- package/dist/stack/install-app.d.ts +22 -0
- package/dist/stack/install-app.d.ts.map +1 -0
- package/dist/stack/install-app.js +262 -0
- package/dist/stack/install-app.js.map +1 -0
- package/dist/stack/integrity.d.ts +94 -0
- package/dist/stack/integrity.d.ts.map +1 -0
- package/dist/stack/integrity.js +268 -0
- package/dist/stack/integrity.js.map +1 -0
- package/dist/{limits.d.ts → stack/limits.d.ts} +2 -5
- package/dist/stack/limits.d.ts.map +1 -0
- package/dist/{limits.js → stack/limits.js} +3 -6
- package/dist/stack/limits.js.map +1 -0
- package/dist/stack/migrations.d.ts +59 -0
- package/dist/stack/migrations.d.ts.map +1 -0
- package/dist/stack/migrations.js +141 -0
- package/dist/stack/migrations.js.map +1 -0
- package/dist/stack/reads.d.ts +69 -0
- package/dist/stack/reads.d.ts.map +1 -0
- package/dist/stack/reads.js +141 -0
- package/dist/stack/reads.js.map +1 -0
- package/dist/stack/record-id.d.ts +35 -0
- package/dist/stack/record-id.d.ts.map +1 -0
- package/dist/stack/record-id.js +82 -0
- package/dist/stack/record-id.js.map +1 -0
- package/dist/stack/stack.d.ts +444 -0
- package/dist/stack/stack.d.ts.map +1 -0
- package/dist/stack/stack.js +1339 -0
- package/dist/stack/stack.js.map +1 -0
- package/dist/stack/system-types.d.ts +16 -0
- package/dist/stack/system-types.d.ts.map +1 -0
- package/dist/stack/system-types.js +111 -0
- package/dist/stack/system-types.js.map +1 -0
- package/dist/stack/type-grants.d.ts +20 -0
- package/dist/stack/type-grants.d.ts.map +1 -0
- package/dist/stack/type-grants.js +69 -0
- package/dist/stack/type-grants.js.map +1 -0
- package/dist/stack/write-expectation.d.ts +79 -0
- package/dist/stack/write-expectation.d.ts.map +1 -0
- package/dist/stack/write-expectation.js +71 -0
- package/dist/stack/write-expectation.js.map +1 -0
- package/dist/stack/write-gate.d.ts +23 -0
- package/dist/stack/write-gate.d.ts.map +1 -0
- package/dist/stack/write-gate.js +17 -0
- package/dist/stack/write-gate.js.map +1 -0
- package/dist/testing.d.ts +44 -33
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +180 -176
- package/dist/testing.js.map +1 -1
- package/dist/type-handle.d.ts +67 -78
- package/dist/type-handle.d.ts.map +1 -1
- package/dist/type-handle.js +44 -111
- package/dist/type-handle.js.map +1 -1
- package/dist/types/adapter.d.ts +282 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/adapter.js +5 -0
- package/dist/types/adapter.js.map +1 -0
- package/dist/types/associations.d.ts +140 -0
- package/dist/types/associations.d.ts.map +1 -0
- package/dist/types/associations.js +6 -0
- package/dist/types/associations.js.map +1 -0
- package/dist/types/capabilities.d.ts +89 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +5 -0
- package/dist/types/capabilities.js.map +1 -0
- package/dist/types/events.d.ts +150 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +5 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/ids.d.ts +43 -0
- package/dist/types/ids.d.ts.map +1 -0
- package/dist/types/ids.js +5 -0
- package/dist/types/ids.js.map +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/journal.d.ts +47 -0
- package/dist/types/journal.d.ts.map +1 -0
- package/dist/types/journal.js +5 -0
- package/dist/types/journal.js.map +1 -0
- package/dist/types/query.d.ts +185 -0
- package/dist/types/query.d.ts.map +1 -0
- package/dist/types/query.js +10 -0
- package/dist/types/query.js.map +1 -0
- package/dist/types/records.d.ts +76 -0
- package/dist/types/records.d.ts.map +1 -0
- package/dist/types/records.js +5 -0
- package/dist/types/records.js.map +1 -0
- package/dist/types/schema.d.ts +74 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +5 -0
- package/dist/types/schema.js.map +1 -0
- package/dist/types/system.d.ts +161 -0
- package/dist/types/system.d.ts.map +1 -0
- package/dist/types/system.js +32 -0
- package/dist/types/system.js.map +1 -0
- package/dist/types/tokens.d.ts +39 -0
- package/dist/types/tokens.d.ts.map +1 -0
- package/dist/types/tokens.js +5 -0
- package/dist/types/tokens.js.map +1 -0
- package/dist/validate.d.ts +31 -70
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +52 -92
- package/dist/validate.js.map +1 -1
- package/dist/{attachment-download.d.ts → wire/attachment-download.d.ts} +0 -57
- package/dist/wire/attachment-download.d.ts.map +1 -0
- package/dist/{attachment-download.js → wire/attachment-download.js} +0 -61
- package/dist/wire/attachment-download.js.map +1 -0
- package/dist/{auth.d.ts → wire/auth.d.ts} +5 -8
- package/dist/wire/auth.d.ts.map +1 -0
- package/dist/{auth.js → wire/auth.js} +5 -8
- package/dist/wire/auth.js.map +1 -0
- package/dist/{wire-body.d.ts → wire/body.d.ts} +35 -12
- package/dist/wire/body.d.ts.map +1 -0
- package/dist/{wire-body.js → wire/body.js} +74 -13
- package/dist/wire/body.js.map +1 -0
- package/dist/{wire-entry.d.ts → wire/index.d.ts} +12 -12
- package/dist/wire/index.d.ts.map +1 -0
- package/dist/{wire-entry.js → wire/index.js} +13 -17
- package/dist/wire/index.js.map +1 -0
- package/dist/wire/record.d.ts +31 -0
- package/dist/wire/record.d.ts.map +1 -0
- package/dist/{wire-record.js → wire/record.js} +7 -67
- package/dist/wire/record.js.map +1 -0
- package/dist/{wire-request.d.ts → wire/request.d.ts} +32 -49
- package/dist/wire/request.d.ts.map +1 -0
- package/dist/{wire-request.js → wire/request.js} +113 -83
- package/dist/wire/request.js.map +1 -0
- package/package.json +7 -7
- package/dist/adapter-entry.d.ts +0 -16
- package/dist/adapter-entry.d.ts.map +0 -1
- package/dist/adapter-entry.js +0 -14
- package/dist/adapter-entry.js.map +0 -1
- package/dist/attachment-download.d.ts.map +0 -1
- package/dist/attachment-download.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/changes.d.ts.map +0 -1
- package/dist/changes.js.map +0 -1
- package/dist/combine.d.ts.map +0 -1
- package/dist/combine.js.map +0 -1
- package/dist/did-entry.d.ts.map +0 -1
- package/dist/did-entry.js.map +0 -1
- package/dist/did.d.ts.map +0 -1
- package/dist/did.js.map +0 -1
- package/dist/identity-bindings.d.ts.map +0 -1
- package/dist/identity-bindings.js.map +0 -1
- package/dist/limits.d.ts.map +0 -1
- package/dist/limits.js.map +0 -1
- package/dist/record-id.d.ts +0 -70
- package/dist/record-id.d.ts.map +0 -1
- package/dist/record-id.js +0 -127
- package/dist/record-id.js.map +0 -1
- package/dist/scoped-stack.d.ts +0 -549
- package/dist/scoped-stack.d.ts.map +0 -1
- package/dist/scoped-stack.js +0 -1301
- package/dist/scoped-stack.js.map +0 -1
- package/dist/sort.d.ts.map +0 -1
- package/dist/sort.js.map +0 -1
- package/dist/stack-reads.d.ts +0 -38
- package/dist/stack-reads.d.ts.map +0 -1
- package/dist/stack-reads.js +0 -79
- package/dist/stack-reads.js.map +0 -1
- package/dist/stack.d.ts +0 -1032
- package/dist/stack.d.ts.map +0 -1
- package/dist/stack.js +0 -2337
- package/dist/stack.js.map +0 -1
- package/dist/types.d.ts +0 -1278
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -51
- package/dist/types.js.map +0 -1
- package/dist/wire-body.d.ts.map +0 -1
- package/dist/wire-body.js.map +0 -1
- package/dist/wire-entry.d.ts.map +0 -1
- package/dist/wire-entry.js.map +0 -1
- package/dist/wire-record.d.ts +0 -50
- package/dist/wire-record.d.ts.map +0 -1
- package/dist/wire-record.js.map +0 -1
- package/dist/wire-request.d.ts.map +0 -1
- package/dist/wire-request.js.map +0 -1
- /package/dist/{did.d.ts → did/did.d.ts} +0 -0
- /package/dist/{identity-bindings.d.ts → stack/identity-bindings.d.ts} +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Association validation
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The rules an association is held to before it is stored: a known kind,
|
|
5
|
+
* only the keys that kind defines, a well-formed target or grantee, the
|
|
6
|
+
* right half of the authority/data partition, and each identity named
|
|
7
|
+
* once. A discriminated union is a compile-time promise, and a server
|
|
8
|
+
* mapping a request body onto one supplies raw JSON no compiler has seen,
|
|
9
|
+
* so the rules live in the invariant layer where no adapter can forget
|
|
10
|
+
* one. See docs/spec/data-model.md § Associations.
|
|
11
|
+
*/
|
|
12
|
+
import type { ValidationError } from '../validate.js';
|
|
13
|
+
import type { Association, AssociationEdit, AuthorityAssociation, DataAssociation, GrantGrantee, RelationshipTarget, RelationshipTargetPattern } from '../types/index.js';
|
|
14
|
+
/** Every key each relationship target arm defines. */
|
|
15
|
+
export declare const TARGET_KEYS: Record<RelationshipTarget['kind'], readonly string[]>;
|
|
16
|
+
/** Every key each grantee arm defines, the default tier included. */
|
|
17
|
+
export declare const GRANTEE_KEYS: Record<GrantGrantee['kind'], readonly string[]>;
|
|
18
|
+
/**
|
|
19
|
+
* The keys of `value` its arm does not define. An element carrying one is
|
|
20
|
+
* refused rather than stored: an adapter keeps only the keys it has
|
|
21
|
+
* columns for, so the rest would answer 200 and then vanish.
|
|
22
|
+
* See docs/spec/data-model.md § Associations.
|
|
23
|
+
*/
|
|
24
|
+
export declare function unknownKeys(value: object, keys: readonly string[]): string[];
|
|
25
|
+
/** unknownKeys() as a refusal. 400, not 422: the key addresses nothing. */
|
|
26
|
+
export declare function assertKnownKeys(value: object, keys: readonly string[], path: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Collect what makes a relationship target malformed. Absence is
|
|
29
|
+
* meaningful on `stackUrl` and an external `id` — this stack, and the
|
|
30
|
+
* whole namespace — so every part that names something must be non-empty:
|
|
31
|
+
* an empty string stores and matches as though it were absent.
|
|
32
|
+
* See docs/spec/data-model.md § Relationship targets.
|
|
33
|
+
*/
|
|
34
|
+
export declare function targetErrors(target: RelationshipTarget | RelationshipTargetPattern, path: string, opts?: {
|
|
35
|
+
externalIdOptional?: boolean;
|
|
36
|
+
}): ValidationError[];
|
|
37
|
+
/**
|
|
38
|
+
* Reject an association outside the closed set the types promise: a request
|
|
39
|
+
* body is raw JSON, and an unknown target kind would otherwise be stored as
|
|
40
|
+
* a Record in this stack. A key its kind does not define is a 400 at every
|
|
41
|
+
* depth, as on a request body. See docs/spec/data-model.md § Associations.
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateAssociation(association: Association, path?: string): ValidationError[];
|
|
44
|
+
/**
|
|
45
|
+
* What makes a grantee malformed, under `path`: first a key its tier does not
|
|
46
|
+
* define — a 400 under `throw`, as on a request body, or collected on a
|
|
47
|
+
* stored `_grant` — then its tier's own fields. `role` has no default to fall
|
|
48
|
+
* back on. `authenticated` is a `_grant` tier alone; `anyRole` admits `'any'`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function granteeErrors(grantee: unknown, path: string, opts: {
|
|
51
|
+
unknownKeys: 'throw' | 'collect';
|
|
52
|
+
authenticated?: boolean;
|
|
53
|
+
anyRole?: boolean;
|
|
54
|
+
}): ValidationError[];
|
|
55
|
+
/**
|
|
56
|
+
* Refuse an authority element on the data surface. The two halves share
|
|
57
|
+
* storage but never a call, or an app editing tags could replace an ACL it
|
|
58
|
+
* was never shown. Thrown, not collected: the caller named the wrong
|
|
59
|
+
* surface, and wanted editAccess()/setAccess().
|
|
60
|
+
* See docs/spec/access-control.md § Record-level permissions.
|
|
61
|
+
*/
|
|
62
|
+
export declare function assertDataAssociations(associations: readonly Association[], surface: string): asserts associations is DataAssociation[];
|
|
63
|
+
/**
|
|
64
|
+
* The mirror of assertDataAssociations(): the `permissions` surface takes
|
|
65
|
+
* authority kinds alone, so a tag routed through it is refused rather than
|
|
66
|
+
* quietly becoming an ACL entry the `associations` projection never shows.
|
|
67
|
+
*/
|
|
68
|
+
export declare function assertAuthorityAssociations(permissions: readonly Association[], surface: string): asserts permissions is AuthorityAssociation[];
|
|
69
|
+
/**
|
|
70
|
+
* Every association in a list, plus the list's own rule: identities are
|
|
71
|
+
* distinct. An adapter keys associations by identity, so a repeat would
|
|
72
|
+
* displace the first and leave which one stands to chance — a caller bug,
|
|
73
|
+
* refused rather than collapsed. See docs/spec/data-model.md § Associations.
|
|
74
|
+
*/
|
|
75
|
+
export declare function validateAssociations(associations: Association[] | undefined, path?: string): ValidationError[];
|
|
76
|
+
/**
|
|
77
|
+
* Hold an edit list to the rules every verb shares: non-empty, known `op`s
|
|
78
|
+
* over well-formed associations, one surface, each identity once — removing
|
|
79
|
+
* and adding one identity is as ambiguous as naming it twice. `repoint` is
|
|
80
|
+
* the journal's word, never a request. See docs/spec/data-model.md § Mutations.
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertAssociationEdits(changes: unknown, surface: string, half: 'data' | 'authority'): asserts changes is AssociationEdit[];
|
|
83
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/associations/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAEV,WAAW,EACX,eAAe,EAEf,oBAAoB,EACpB,eAAe,EAGf,YAAY,EAKZ,kBAAkB,EAClB,yBAAyB,EAE1B,MAAM,mBAAmB,CAAC;AAY3B,sDAAsD;AACtD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAI7E,CAAC;AAgBF,qEAAqE;AACrE,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAIxE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAE5E;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM1F;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,kBAAkB,GAAG,yBAAyB,EACtD,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1C,eAAe,EAAE,CAyBnB;AAmBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,WAAW,EACxB,IAAI,SAAgB,GACnB,eAAe,EAAE,CAoBnB;AAmCD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IAAE,WAAW,EAAE,OAAO,GAAG,SAAS,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACrF,eAAe,EAAE,CA+CnB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,SAAS,WAAW,EAAE,EACpC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,IAAI,eAAe,EAAE,CAO3C;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,SAAS,WAAW,EAAE,EACnC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,IAAI,oBAAoB,EAAE,CAO/C;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,EACvC,IAAI,SAAiB,GACpB,eAAe,EAAE,CAMnB;AAgBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GAAG,WAAW,GACzB,OAAO,CAAC,OAAO,IAAI,eAAe,EAAE,CAoCtC"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Association validation
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The rules an association is held to before it is stored: a known kind,
|
|
5
|
+
* only the keys that kind defines, a well-formed target or grantee, the
|
|
6
|
+
* right half of the authority/data partition, and each identity named
|
|
7
|
+
* once. A discriminated union is a compile-time promise, and a server
|
|
8
|
+
* mapping a request body onto one supplies raw JSON no compiler has seen,
|
|
9
|
+
* so the rules live in the invariant layer where no adapter can forget
|
|
10
|
+
* one. See docs/spec/data-model.md § Associations.
|
|
11
|
+
*/
|
|
12
|
+
import { ARGUMENTS_INVALID, StackBadRequestError, StackValidationError } from '../errors.js';
|
|
13
|
+
import { associationEqual, isAuthorityAssociation } from './identity.js';
|
|
14
|
+
/** The identifier spaces a relationship target may name. */
|
|
15
|
+
const TARGET_KINDS = new Set(['record', 'entity', 'external']);
|
|
16
|
+
/**
|
|
17
|
+
* The keys of one arm of a union, listed as an object so the compiler
|
|
18
|
+
* holds the list to the type: a field added to the arm fails to compile
|
|
19
|
+
* here until it is listed.
|
|
20
|
+
*/
|
|
21
|
+
const keysOf = (keys) => Object.keys(keys);
|
|
22
|
+
/** Every key each relationship target arm defines. */
|
|
23
|
+
export const TARGET_KEYS = {
|
|
24
|
+
record: keysOf({ kind: true, recordId: true, stackUrl: true }),
|
|
25
|
+
entity: keysOf({ kind: true, entityId: true }),
|
|
26
|
+
external: keysOf({ kind: true, ns: true, id: true }),
|
|
27
|
+
};
|
|
28
|
+
/** Every key each association kind defines. */
|
|
29
|
+
const ASSOCIATION_KEYS = {
|
|
30
|
+
tag: keysOf({ kind: true, label: true }),
|
|
31
|
+
attachment: keysOf({
|
|
32
|
+
kind: true,
|
|
33
|
+
label: true,
|
|
34
|
+
fileId: true,
|
|
35
|
+
attachmentRecordId: true,
|
|
36
|
+
}),
|
|
37
|
+
relationship: keysOf({ kind: true, label: true, target: true }),
|
|
38
|
+
permission: keysOf({ kind: true, label: true, grantee: true }),
|
|
39
|
+
anyone: keysOf({ kind: true, label: true }),
|
|
40
|
+
};
|
|
41
|
+
/** Every key each grantee arm defines, the default tier included. */
|
|
42
|
+
export const GRANTEE_KEYS = {
|
|
43
|
+
entity: keysOf({ kind: true, entityId: true }),
|
|
44
|
+
group: keysOf({ kind: true, groupId: true, role: true }),
|
|
45
|
+
authenticated: keysOf({ kind: true }),
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The keys of `value` its arm does not define. An element carrying one is
|
|
49
|
+
* refused rather than stored: an adapter keeps only the keys it has
|
|
50
|
+
* columns for, so the rest would answer 200 and then vanish.
|
|
51
|
+
* See docs/spec/data-model.md § Associations.
|
|
52
|
+
*/
|
|
53
|
+
export function unknownKeys(value, keys) {
|
|
54
|
+
return Object.keys(value).filter((key) => !keys.includes(key));
|
|
55
|
+
}
|
|
56
|
+
/** unknownKeys() as a refusal. 400, not 422: the key addresses nothing. */
|
|
57
|
+
export function assertKnownKeys(value, keys, path) {
|
|
58
|
+
const unknown = unknownKeys(value, keys);
|
|
59
|
+
if (unknown.length > 0)
|
|
60
|
+
throw new StackBadRequestError(`Unknown key${unknown.length > 1 ? 's' : ''} in ${path}: ${unknown.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Collect what makes a relationship target malformed. Absence is
|
|
64
|
+
* meaningful on `stackUrl` and an external `id` — this stack, and the
|
|
65
|
+
* whole namespace — so every part that names something must be non-empty:
|
|
66
|
+
* an empty string stores and matches as though it were absent.
|
|
67
|
+
* See docs/spec/data-model.md § Relationship targets.
|
|
68
|
+
*/
|
|
69
|
+
export function targetErrors(target, path, opts = {}) {
|
|
70
|
+
const fail = (message) => [{ path, message }];
|
|
71
|
+
if (!target || typeof target !== 'object')
|
|
72
|
+
return fail('A relationship target must be an object.');
|
|
73
|
+
if (!TARGET_KINDS.has(target.kind)) {
|
|
74
|
+
return fail(`Unknown relationship target kind "${target.kind}": expected "record", "entity" or "external".`);
|
|
75
|
+
}
|
|
76
|
+
assertKnownKeys(target, TARGET_KEYS[target.kind], path);
|
|
77
|
+
if (target.kind === 'record') {
|
|
78
|
+
if (!target.recordId)
|
|
79
|
+
return fail('A record target requires a non-empty recordId.');
|
|
80
|
+
if (target.stackUrl !== undefined && !target.stackUrl) {
|
|
81
|
+
return fail("A record target's stackUrl must be non-empty; omit it to name this stack.");
|
|
82
|
+
}
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
if (target.kind === 'entity') {
|
|
86
|
+
return target.entityId ? [] : fail('An entity target requires a non-empty entityId.');
|
|
87
|
+
}
|
|
88
|
+
if (!target.ns)
|
|
89
|
+
return fail('An external target requires a non-empty ns.');
|
|
90
|
+
if (target.id === undefined) {
|
|
91
|
+
return opts.externalIdOptional ? [] : fail('An external target requires an id.');
|
|
92
|
+
}
|
|
93
|
+
return target.id ? [] : fail("An external target's id must be non-empty when present.");
|
|
94
|
+
}
|
|
95
|
+
/** The kinds an association may name — the closed set every surface reads. */
|
|
96
|
+
const ASSOCIATION_KINDS = new Set(['tag', 'attachment', 'relationship', 'permission', 'anyone']);
|
|
97
|
+
/**
|
|
98
|
+
* Whether a value is an association at all. Asked ahead of the partition
|
|
99
|
+
* checks, so a malformed element is named as one rather than reported as
|
|
100
|
+
* the wrong surface for a kind it never had — a request body supplies raw
|
|
101
|
+
* JSON, and `null` or `{}` is neither half of the partition.
|
|
102
|
+
*/
|
|
103
|
+
function namesKind(association) {
|
|
104
|
+
return (!!association &&
|
|
105
|
+
typeof association === 'object' &&
|
|
106
|
+
ASSOCIATION_KINDS.has(association.kind));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Reject an association outside the closed set the types promise: a request
|
|
110
|
+
* body is raw JSON, and an unknown target kind would otherwise be stored as
|
|
111
|
+
* a Record in this stack. A key its kind does not define is a 400 at every
|
|
112
|
+
* depth, as on a request body. See docs/spec/data-model.md § Associations.
|
|
113
|
+
*/
|
|
114
|
+
export function validateAssociation(association, path = 'association') {
|
|
115
|
+
if (!namesKind(association)) {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
path: `${path}.kind`,
|
|
119
|
+
message: `Unknown association kind "${String(association?.kind)}": expected ${[...ASSOCIATION_KINDS].map((k) => `"${k}"`).join(', ')}.`,
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
assertKnownKeys(association, ASSOCIATION_KEYS[association.kind], path);
|
|
124
|
+
if (association.kind === 'permission') {
|
|
125
|
+
return permissionErrors(association, path);
|
|
126
|
+
}
|
|
127
|
+
if (association.kind === 'anyone') {
|
|
128
|
+
return association.label === 'read'
|
|
129
|
+
? []
|
|
130
|
+
: [{ path: `${path}.label`, message: 'An `anyone` association carries only `read`.' }];
|
|
131
|
+
}
|
|
132
|
+
if (association.kind !== 'relationship')
|
|
133
|
+
return [];
|
|
134
|
+
return targetErrors(association.target, `${path}.target`);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Who a duplicated permission names, since kind and label alone don't
|
|
138
|
+
* distinguish two grants of the same bit to different grantees.
|
|
139
|
+
*/
|
|
140
|
+
function granteeSuffix(association) {
|
|
141
|
+
if (association.kind !== 'permission')
|
|
142
|
+
return '';
|
|
143
|
+
const g = association.grantee;
|
|
144
|
+
return g.kind === 'entity' ? ` for "${g.entityId}"` : ` for ${g.role}s of "${g.groupId}"`;
|
|
145
|
+
}
|
|
146
|
+
/** The bits a permission element may name — the whole of its meaning. */
|
|
147
|
+
const PERMISSION_LABELS = new Set(['read', 'write']);
|
|
148
|
+
/** A permission element's label and grantee — see granteeErrors(). */
|
|
149
|
+
function permissionErrors(association, path) {
|
|
150
|
+
if (!PERMISSION_LABELS.has(association.label)) {
|
|
151
|
+
return [
|
|
152
|
+
{
|
|
153
|
+
path: `${path}.label`,
|
|
154
|
+
message: `Unknown permission label "${association.label}": expected "read" or "write".`,
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
const grantee = association.grantee;
|
|
159
|
+
if (!grantee || typeof grantee !== 'object') {
|
|
160
|
+
return [{ path: `${path}.grantee`, message: 'A permission requires a grantee.' }];
|
|
161
|
+
}
|
|
162
|
+
return granteeErrors(grantee, `${path}.grantee`, { unknownKeys: 'throw' });
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* What makes a grantee malformed, under `path`: first a key its tier does not
|
|
166
|
+
* define — a 400 under `throw`, as on a request body, or collected on a
|
|
167
|
+
* stored `_grant` — then its tier's own fields. `role` has no default to fall
|
|
168
|
+
* back on. `authenticated` is a `_grant` tier alone; `anyRole` admits `'any'`.
|
|
169
|
+
*/
|
|
170
|
+
export function granteeErrors(grantee, path, opts) {
|
|
171
|
+
const g = (grantee ?? {});
|
|
172
|
+
const tier = g.kind === 'entity' || g.kind === 'group' || (opts.authenticated && g.kind === 'authenticated')
|
|
173
|
+
? g.kind
|
|
174
|
+
: null;
|
|
175
|
+
if (tier === null) {
|
|
176
|
+
return [
|
|
177
|
+
{
|
|
178
|
+
path: `${path}.kind`,
|
|
179
|
+
message: opts.authenticated
|
|
180
|
+
? "A grantee must name its tier: 'entity', 'group' or 'authenticated'."
|
|
181
|
+
: "A grantee must name its tier: 'entity' or 'group'.",
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
if (opts.unknownKeys === 'throw')
|
|
186
|
+
assertKnownKeys(g, GRANTEE_KEYS[tier], path);
|
|
187
|
+
const unknown = unknownKeys(g, GRANTEE_KEYS[tier]);
|
|
188
|
+
if (unknown.length > 0) {
|
|
189
|
+
const article = tier === 'group' ? 'A' : 'An';
|
|
190
|
+
return unknown.map((key) => ({
|
|
191
|
+
path: `${path}.${key}`,
|
|
192
|
+
message: `${article} ${tier} grantee does not carry ${key}.`,
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
if (tier === 'authenticated')
|
|
196
|
+
return [];
|
|
197
|
+
if (tier === 'entity') {
|
|
198
|
+
return typeof g.entityId === 'string' && g.entityId.length > 0
|
|
199
|
+
? []
|
|
200
|
+
: [{ path: `${path}.entityId`, message: 'An entity grantee requires a non-empty entityId.' }];
|
|
201
|
+
}
|
|
202
|
+
const errors = [];
|
|
203
|
+
if (typeof g.groupId !== 'string' || g.groupId.length === 0) {
|
|
204
|
+
errors.push({
|
|
205
|
+
path: `${path}.groupId`,
|
|
206
|
+
message: 'A group grantee requires a non-empty groupId.',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (g.role !== 'member' && g.role !== 'admin' && !(opts.anyRole && g.role === 'any')) {
|
|
210
|
+
errors.push({
|
|
211
|
+
path: `${path}.role`,
|
|
212
|
+
message: opts.anyRole
|
|
213
|
+
? "A group grantee requires role 'member', 'admin' or 'any'."
|
|
214
|
+
: "A group grantee requires role 'member' or 'admin'.",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return errors;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Refuse an authority element on the data surface. The two halves share
|
|
221
|
+
* storage but never a call, or an app editing tags could replace an ACL it
|
|
222
|
+
* was never shown. Thrown, not collected: the caller named the wrong
|
|
223
|
+
* surface, and wanted editAccess()/setAccess().
|
|
224
|
+
* See docs/spec/access-control.md § Record-level permissions.
|
|
225
|
+
*/
|
|
226
|
+
export function assertDataAssociations(associations, surface) {
|
|
227
|
+
const authority = associations.find((a) => namesKind(a) && isAuthorityAssociation(a));
|
|
228
|
+
if (!authority)
|
|
229
|
+
return;
|
|
230
|
+
throw new StackBadRequestError(`${surface} does not carry authority: a "${authority.kind}" association belongs to the ` +
|
|
231
|
+
'`permissions` surface — use editAccess() or setAccess().');
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The mirror of assertDataAssociations(): the `permissions` surface takes
|
|
235
|
+
* authority kinds alone, so a tag routed through it is refused rather than
|
|
236
|
+
* quietly becoming an ACL entry the `associations` projection never shows.
|
|
237
|
+
*/
|
|
238
|
+
export function assertAuthorityAssociations(permissions, surface) {
|
|
239
|
+
const data = permissions.find((a) => namesKind(a) && !isAuthorityAssociation(a));
|
|
240
|
+
if (!data)
|
|
241
|
+
return;
|
|
242
|
+
throw new StackBadRequestError(`${surface} carries authority alone: a "${data.kind}" association belongs to the ` +
|
|
243
|
+
'`associations` surface — use editAssociations() or setAssociations().');
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Every association in a list, plus the list's own rule: identities are
|
|
247
|
+
* distinct. An adapter keys associations by identity, so a repeat would
|
|
248
|
+
* displace the first and leave which one stands to chance — a caller bug,
|
|
249
|
+
* refused rather than collapsed. See docs/spec/data-model.md § Associations.
|
|
250
|
+
*/
|
|
251
|
+
export function validateAssociations(associations, path = 'associations') {
|
|
252
|
+
const list = associations ?? [];
|
|
253
|
+
return [
|
|
254
|
+
...list.flatMap((a, i) => validateAssociation(a, `${path}[${i}]`)),
|
|
255
|
+
...duplicateIdentityErrors(list, path),
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
/** Each element naming an identity an earlier element already named. */
|
|
259
|
+
function duplicateIdentityErrors(list, path) {
|
|
260
|
+
return list.flatMap((a, i) => list.slice(0, i).some((b) => associationEqual(a, b))
|
|
261
|
+
? [
|
|
262
|
+
{
|
|
263
|
+
path: `${path}[${i}]`,
|
|
264
|
+
message: `Duplicate association identity: ${a.kind} "${a.label}"${granteeSuffix(a)} is named more than once.`,
|
|
265
|
+
},
|
|
266
|
+
]
|
|
267
|
+
: []);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Hold an edit list to the rules every verb shares: non-empty, known `op`s
|
|
271
|
+
* over well-formed associations, one surface, each identity once — removing
|
|
272
|
+
* and adding one identity is as ambiguous as naming it twice. `repoint` is
|
|
273
|
+
* the journal's word, never a request. See docs/spec/data-model.md § Mutations.
|
|
274
|
+
*/
|
|
275
|
+
export function assertAssociationEdits(changes, surface, half) {
|
|
276
|
+
if (!Array.isArray(changes) || changes.length === 0) {
|
|
277
|
+
throw new StackValidationError([{ path: 'changes', message: `${surface} names at least one change.` }], ARGUMENTS_INVALID);
|
|
278
|
+
}
|
|
279
|
+
const errors = [];
|
|
280
|
+
changes.forEach((raw, i) => {
|
|
281
|
+
const path = `changes[${i}]`;
|
|
282
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
283
|
+
errors.push({ path, message: `${path} must be an object.` });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const edit = raw;
|
|
287
|
+
if (edit.op !== 'repoint')
|
|
288
|
+
assertKnownKeys(edit, ['op', 'association'], path);
|
|
289
|
+
if (edit.op === 'repoint') {
|
|
290
|
+
errors.push({
|
|
291
|
+
path: `${path}.op`,
|
|
292
|
+
message: 'op: "repoint" is recorded by the journal, not requested. ' +
|
|
293
|
+
"Send { op: 'add', association }: an add naming an attachment the record already holds re-points it in place.",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
else if (edit.op !== 'add' && edit.op !== 'remove') {
|
|
297
|
+
errors.push({ path: `${path}.op`, message: 'op must be "add" or "remove".' });
|
|
298
|
+
}
|
|
299
|
+
errors.push(...validateAssociation(edit.association, `${path}.association`));
|
|
300
|
+
});
|
|
301
|
+
if (errors.length > 0)
|
|
302
|
+
throw new StackValidationError(errors, ARGUMENTS_INVALID);
|
|
303
|
+
const associations = changes.map((c) => c.association);
|
|
304
|
+
if (half === 'data')
|
|
305
|
+
assertDataAssociations(associations, surface);
|
|
306
|
+
else
|
|
307
|
+
assertAuthorityAssociations(associations, surface);
|
|
308
|
+
const duplicates = duplicateIdentityErrors(associations, 'changes');
|
|
309
|
+
if (duplicates.length > 0)
|
|
310
|
+
throw new StackValidationError(duplicates, ARGUMENTS_INVALID);
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/associations/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment metadata records
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Which of a fileId's `_attachment` records answers for it: the one that
|
|
5
|
+
* establishes its mimeType, and the one a given reference names. Pure and
|
|
6
|
+
* generic over the record shape, so `Stack` and a server's own rows reach
|
|
7
|
+
* the same answer — `@haverstack/core/wire` exports the selections, and
|
|
8
|
+
* this module imports nothing so that entry point stays light.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Which `_attachment@1` record establishes a fileId's mimeType: earliest
|
|
12
|
+
* `createdAt`, ties to the lower `id`. Two racing first uploads can both
|
|
13
|
+
* land, so determinism rests on core's conflict check and every server
|
|
14
|
+
* ordering through here. The caller filters to one fileId.
|
|
15
|
+
* See docs/spec/attachments.md § Finding a `fileId`'s metadata records.
|
|
16
|
+
*/
|
|
17
|
+
export declare function firstRecordedAttachment<T extends {
|
|
18
|
+
id: string;
|
|
19
|
+
createdAt: Date;
|
|
20
|
+
}>(records: readonly T[]): T | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The same total order as a comparator, for a caller handing back the whole
|
|
23
|
+
* candidate set rather than the winner — sorted this way, the winner is
|
|
24
|
+
* element zero. Package-internal: `core/wire` exports the selection,
|
|
25
|
+
* since the ordering is only useful to something that already has every
|
|
26
|
+
* record.
|
|
27
|
+
*/
|
|
28
|
+
export declare function compareRecordedAttachments<T extends {
|
|
29
|
+
id: string;
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
}>(a: T, b: T): number;
|
|
32
|
+
/**
|
|
33
|
+
* Which `_attachment` record a *reference* to a fileId names: its
|
|
34
|
+
* `attachmentRecordId`, else the requester's own upload, else the
|
|
35
|
+
* first-recorded — each step falling back rather than failing.
|
|
36
|
+
* See docs/spec/attachments.md § Naming the upload a reference came from.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveReferencedAttachment<T extends {
|
|
39
|
+
id: string;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
createdBy?: {
|
|
42
|
+
subjectId: string;
|
|
43
|
+
};
|
|
44
|
+
}>(records: readonly T[], opts?: {
|
|
45
|
+
attachmentRecordId?: string;
|
|
46
|
+
requesterEntityId?: string;
|
|
47
|
+
}): T | undefined;
|
|
48
|
+
//# sourceMappingURL=attachment-records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-records.d.ts","sourceRoot":"","sources":["../src/attachment-records.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,EAC/E,OAAO,EAAE,SAAS,CAAC,EAAE,GACpB,CAAC,GAAG,SAAS,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,EAClF,CAAC,EAAE,CAAC,EACJ,CAAC,EAAE,CAAC,GACH,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EAE5E,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,IAAI,GAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GACrE,CAAC,GAAG,SAAS,CAUf"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment metadata records
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Which of a fileId's `_attachment` records answers for it: the one that
|
|
5
|
+
* establishes its mimeType, and the one a given reference names. Pure and
|
|
6
|
+
* generic over the record shape, so `Stack` and a server's own rows reach
|
|
7
|
+
* the same answer — `@haverstack/core/wire` exports the selections, and
|
|
8
|
+
* this module imports nothing so that entry point stays light.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Which `_attachment@1` record establishes a fileId's mimeType: earliest
|
|
12
|
+
* `createdAt`, ties to the lower `id`. Two racing first uploads can both
|
|
13
|
+
* land, so determinism rests on core's conflict check and every server
|
|
14
|
+
* ordering through here. The caller filters to one fileId.
|
|
15
|
+
* See docs/spec/attachments.md § Finding a `fileId`'s metadata records.
|
|
16
|
+
*/
|
|
17
|
+
export function firstRecordedAttachment(records) {
|
|
18
|
+
return records.reduce((first, record) => (!first || compareRecordedAttachments(record, first) < 0 ? record : first), undefined);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The same total order as a comparator, for a caller handing back the whole
|
|
22
|
+
* candidate set rather than the winner — sorted this way, the winner is
|
|
23
|
+
* element zero. Package-internal: `core/wire` exports the selection,
|
|
24
|
+
* since the ordering is only useful to something that already has every
|
|
25
|
+
* record.
|
|
26
|
+
*/
|
|
27
|
+
export function compareRecordedAttachments(a, b) {
|
|
28
|
+
const delta = a.createdAt.getTime() - b.createdAt.getTime();
|
|
29
|
+
if (delta !== 0)
|
|
30
|
+
return delta;
|
|
31
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Which `_attachment` record a *reference* to a fileId names: its
|
|
35
|
+
* `attachmentRecordId`, else the requester's own upload, else the
|
|
36
|
+
* first-recorded — each step falling back rather than failing.
|
|
37
|
+
* See docs/spec/attachments.md § Naming the upload a reference came from.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveReferencedAttachment(records, opts = {}) {
|
|
40
|
+
if (opts.attachmentRecordId) {
|
|
41
|
+
const named = records.find((record) => record.id === opts.attachmentRecordId);
|
|
42
|
+
if (named)
|
|
43
|
+
return named;
|
|
44
|
+
}
|
|
45
|
+
if (opts.requesterEntityId) {
|
|
46
|
+
const own = records.filter((record) => record.createdBy?.subjectId === opts.requesterEntityId);
|
|
47
|
+
if (own.length > 0)
|
|
48
|
+
return firstRecordedAttachment(own);
|
|
49
|
+
}
|
|
50
|
+
return firstRecordedAttachment(records);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=attachment-records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-records.js","sourceRoot":"","sources":["../src/attachment-records.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAqB;IAErB,OAAO,OAAO,CAAC,MAAM,CACnB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,0BAA0B,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAC7F,SAAS,CACV,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,CAAI,EACJ,CAAI;IAEJ,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IAC5D,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CAGzC,OAAqB,EACrB,OAAoE,EAAE;IAEtE,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC9E,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC/F,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC"}
|