@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.
- package/README.md +9 -2
- package/dist/access.d.ts +20 -0
- package/dist/access.d.ts.map +1 -1
- package/dist/access.js +26 -0
- package/dist/access.js.map +1 -1
- package/dist/adapter-entry.d.ts +1 -1
- package/dist/adapter-entry.d.ts.map +1 -1
- package/dist/adapter-entry.js +1 -1
- package/dist/adapter-entry.js.map +1 -1
- package/dist/changes.d.ts +15 -2
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +62 -6
- package/dist/changes.js.map +1 -1
- package/dist/combine.d.ts.map +1 -1
- package/dist/combine.js +1 -4
- package/dist/combine.js.map +1 -1
- package/dist/errors.d.ts +173 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +208 -0
- package/dist/errors.js.map +1 -0
- package/dist/grants.d.ts +84 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +157 -0
- package/dist/grants.js.map +1 -0
- package/dist/id.d.ts +7 -5
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +17 -15
- package/dist/id.js.map +1 -1
- package/dist/identity-bindings.d.ts +13 -0
- package/dist/identity-bindings.d.ts.map +1 -0
- package/dist/identity-bindings.js +42 -0
- package/dist/identity-bindings.js.map +1 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +23 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +35 -0
- package/dist/limits.js.map +1 -0
- package/dist/merge.d.ts +1 -1
- package/dist/merge.js +2 -2
- package/dist/merge.js.map +1 -1
- package/dist/query-validation.d.ts +80 -0
- package/dist/query-validation.d.ts.map +1 -0
- package/dist/query-validation.js +223 -0
- package/dist/query-validation.js.map +1 -0
- package/dist/record-changes.d.ts +89 -0
- package/dist/record-changes.d.ts.map +1 -0
- package/dist/record-changes.js +201 -0
- package/dist/record-changes.js.map +1 -0
- package/dist/record-id.d.ts +70 -0
- package/dist/record-id.d.ts.map +1 -0
- package/dist/record-id.js +127 -0
- package/dist/record-id.js.map +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +56 -5
- package/dist/schema.js.map +1 -1
- package/dist/scoped-stack.d.ts +460 -0
- package/dist/scoped-stack.d.ts.map +1 -0
- package/dist/scoped-stack.js +1163 -0
- package/dist/scoped-stack.js.map +1 -0
- package/dist/stack-reads.d.ts +38 -0
- package/dist/stack-reads.d.ts.map +1 -0
- package/dist/stack-reads.js +79 -0
- package/dist/stack-reads.js.map +1 -0
- package/dist/stack.d.ts +108 -700
- package/dist/stack.d.ts.map +1 -1
- package/dist/stack.js +235 -2286
- package/dist/stack.js.map +1 -1
- package/dist/testing.d.ts +3 -14
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +30 -42
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +107 -30
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -1
- package/dist/validate.d.ts +31 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +150 -3
- package/dist/validate.js.map +1 -1
- package/dist/wire-entry.d.ts +3 -3
- package/dist/wire-entry.d.ts.map +1 -1
- package/dist/wire-entry.js +3 -3
- package/dist/wire-entry.js.map +1 -1
- package/dist/wire-record.d.ts +16 -1
- package/dist/wire-record.d.ts.map +1 -1
- package/dist/wire-record.js +57 -1
- package/dist/wire-record.js.map +1 -1
- package/dist/wire-request.d.ts.map +1 -1
- package/dist/wire-request.js +3 -2
- package/dist/wire-request.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,201 @@
|
|
|
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 { baseIdOf } from './schema.js';
|
|
16
|
+
import { StackQueryError } from './errors.js';
|
|
17
|
+
import { SYSTEM_TYPES, RECORD_CHANGE_KEYS } from './types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Matches the SQLite adapter's association primary key (kind, label,
|
|
20
|
+
* file_id, related_scope, related_id, related_ns, related_stack).
|
|
21
|
+
*/
|
|
22
|
+
export function associationEqual(a, b) {
|
|
23
|
+
if (a.kind !== b.kind || a.label !== b.label)
|
|
24
|
+
return false;
|
|
25
|
+
if (a.kind === 'attachment' && b.kind === 'attachment')
|
|
26
|
+
return a.fileId === b.fileId;
|
|
27
|
+
if (a.kind === 'relationship' && b.kind === 'relationship') {
|
|
28
|
+
return targetEqual(a.target, b.target);
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
/** Structural equality per target arm — what dissociate() matches on. */
|
|
33
|
+
export function targetEqual(a, b) {
|
|
34
|
+
if (a.scope !== b.scope)
|
|
35
|
+
return false;
|
|
36
|
+
if (a.scope === 'record' && b.scope === 'record') {
|
|
37
|
+
return a.recordId === b.recordId && (a.stackUrl ?? '') === (b.stackUrl ?? '');
|
|
38
|
+
}
|
|
39
|
+
if (a.scope === 'entity' && b.scope === 'entity')
|
|
40
|
+
return a.entityId === b.entityId;
|
|
41
|
+
if (a.scope === 'external' && b.scope === 'external')
|
|
42
|
+
return a.ns === b.ns && a.id === b.id;
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
export function permissionEqual(a, b) {
|
|
46
|
+
if (a.access !== b.access)
|
|
47
|
+
return false;
|
|
48
|
+
if (a.access === 'public')
|
|
49
|
+
return true;
|
|
50
|
+
if (a.access === 'entity' && b.access === 'entity') {
|
|
51
|
+
return a.entityId === b.entityId && a.read === b.read && a.write === b.write;
|
|
52
|
+
}
|
|
53
|
+
if (a.access === 'group' && b.access === 'group') {
|
|
54
|
+
return a.groupId === b.groupId && a.role === b.role && a.read === b.read && a.write === b.write;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A change set has to name at least one aspect. Refused rather than read
|
|
60
|
+
* as a no-op: it addresses nothing, so there is nothing it could have
|
|
61
|
+
* failed to satisfy, and every way of producing one is a caller bug —
|
|
62
|
+
* typically a conditional that built an empty object.
|
|
63
|
+
* See docs/spec/data-model.md § Mutations.
|
|
64
|
+
*/
|
|
65
|
+
export function assertNonEmptyChangeSet(changes) {
|
|
66
|
+
// Presence, not truthiness: `unlisted: false` and `parentId: null` are
|
|
67
|
+
// aspects this call names, and reading them as absent would drop a
|
|
68
|
+
// change the caller asked for.
|
|
69
|
+
if (RECORD_CHANGE_KEYS.some((key) => changes[key] !== undefined))
|
|
70
|
+
return;
|
|
71
|
+
throw new StackQueryError('A change set names at least one of: ' + RECORD_CHANGE_KEYS.join(', ') + '.');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Which aspects a change set actually moves, against the record as it
|
|
75
|
+
* stands — the no-op decision and the change event's `ops` are the same
|
|
76
|
+
* comparison, so they can never disagree. A key naming the value a record
|
|
77
|
+
* already holds contributes nothing.
|
|
78
|
+
*
|
|
79
|
+
* `merged` is the content the patch produces, computed once by the
|
|
80
|
+
* caller that had to validate it anyway.
|
|
81
|
+
* See docs/spec/events.md § The event shape.
|
|
82
|
+
*/
|
|
83
|
+
export function changeSetOps(existing, changes, merged) {
|
|
84
|
+
const ops = [];
|
|
85
|
+
if (merged !== undefined && !contentEqual(existing.content, merged))
|
|
86
|
+
ops.push('patch');
|
|
87
|
+
if (changes.parentId !== undefined && changes.parentId !== (existing.parentId ?? null)) {
|
|
88
|
+
ops.push('reparent');
|
|
89
|
+
}
|
|
90
|
+
if (changes.permissions && !permissionsEqual(existing.permissions ?? [], changes.permissions)) {
|
|
91
|
+
ops.push('permissions');
|
|
92
|
+
}
|
|
93
|
+
if (changes.associations) {
|
|
94
|
+
const before = existing.associations ?? [];
|
|
95
|
+
const after = changes.associations;
|
|
96
|
+
if (after.some((a) => !before.some((b) => associationEqual(a, b))))
|
|
97
|
+
ops.push('associate');
|
|
98
|
+
if (before.some((b) => !after.some((a) => associationEqual(a, b))))
|
|
99
|
+
ops.push('dissociate');
|
|
100
|
+
}
|
|
101
|
+
if (changes.unlisted !== undefined && Boolean(existing.unlistedAt) !== changes.unlisted) {
|
|
102
|
+
ops.push(changes.unlisted ? 'unlist' : 'list');
|
|
103
|
+
}
|
|
104
|
+
return ops;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The change set narrowed to the aspects that actually moved. An adapter
|
|
108
|
+
* is handed this rather than what the caller wrote, so restating an aspect
|
|
109
|
+
* cannot rewrite it: `unlisted: true` on an already-unlisted record would
|
|
110
|
+
* otherwise drag `unlistedAt` forward, moving the record's publish moment
|
|
111
|
+
* with no op reporting it. It also keeps the adapter contract honest —
|
|
112
|
+
* every key an adapter receives is one it must write.
|
|
113
|
+
*/
|
|
114
|
+
export function effectiveChanges(changes, ops) {
|
|
115
|
+
const effective = {};
|
|
116
|
+
if (ops.includes('patch'))
|
|
117
|
+
effective.contentPatch = changes.contentPatch;
|
|
118
|
+
if (ops.includes('reparent'))
|
|
119
|
+
effective.parentId = changes.parentId;
|
|
120
|
+
if (ops.includes('permissions'))
|
|
121
|
+
effective.permissions = changes.permissions;
|
|
122
|
+
if (ops.includes('associate') || ops.includes('dissociate')) {
|
|
123
|
+
effective.associations = changes.associations;
|
|
124
|
+
}
|
|
125
|
+
if (ops.includes('unlist') || ops.includes('list'))
|
|
126
|
+
effective.unlisted = changes.unlisted;
|
|
127
|
+
return effective;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Whether a merge patch produced the content the record already held.
|
|
131
|
+
* Compared by serialization: content is JSON by construction — it round
|
|
132
|
+
* trips through storage that way — and a patch preserves key order for
|
|
133
|
+
* every field it does not name, so the encoding of an unchanged record is
|
|
134
|
+
* stable. A reordering patch that changes nothing else is the one case
|
|
135
|
+
* this reports as a change, which costs an empty version rather than a
|
|
136
|
+
* wrong answer.
|
|
137
|
+
*/
|
|
138
|
+
export function contentEqual(a, b) {
|
|
139
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
140
|
+
}
|
|
141
|
+
export function permissionsEqual(a, b) {
|
|
142
|
+
return a.length === b.length && a.every((p, i) => permissionEqual(p, b[i]));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Ensures `creator` carries an `admin` relationship association, adding one
|
|
146
|
+
* if it's not already present. Used to bootstrap a `_group` record's first
|
|
147
|
+
* admin at create time.
|
|
148
|
+
*/
|
|
149
|
+
export function stampGroupAdmin(associations, creator) {
|
|
150
|
+
const list = associations ?? [];
|
|
151
|
+
const alreadyAdmin = list.some((a) => a.kind === 'relationship' &&
|
|
152
|
+
a.label === 'admin' &&
|
|
153
|
+
a.target.scope === 'entity' &&
|
|
154
|
+
a.target.entityId === creator);
|
|
155
|
+
if (alreadyAdmin)
|
|
156
|
+
return list;
|
|
157
|
+
return [
|
|
158
|
+
...list,
|
|
159
|
+
{ kind: 'relationship', label: 'admin', target: { scope: 'entity', entityId: creator } },
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Whether a Record is a `_group`, in any of its type versions — the family
|
|
164
|
+
* whose roster rules apply.
|
|
165
|
+
*/
|
|
166
|
+
export function isGroupRecord(record) {
|
|
167
|
+
return baseIdOf(record.typeId) === SYSTEM_TYPES.GROUP;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Drops a snapshot's `permissions` — owner-only audit data, never served
|
|
171
|
+
* to a non-owner history reader. `entityId` (change attribution) stays.
|
|
172
|
+
* See docs/spec/versioning.md § History access.
|
|
173
|
+
*/
|
|
174
|
+
export function stripVersionPermissions(version) {
|
|
175
|
+
if (version.permissions === undefined)
|
|
176
|
+
return version;
|
|
177
|
+
const { permissions: _permissions, ...rest } = version;
|
|
178
|
+
return rest;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The tombstone a soft-deleted Record is presented as. `permissions` is
|
|
182
|
+
* retained because it decides whether the caller may undelete; history is
|
|
183
|
+
* exempt, which is why getVersions() still serves the content this
|
|
184
|
+
* withholds. See docs/spec/versioning.md § The tombstone is literal.
|
|
185
|
+
*/
|
|
186
|
+
export function tombstoneOf(record) {
|
|
187
|
+
return {
|
|
188
|
+
id: record.id,
|
|
189
|
+
typeId: record.typeId,
|
|
190
|
+
createdAt: record.createdAt,
|
|
191
|
+
updatedAt: record.updatedAt,
|
|
192
|
+
version: record.version,
|
|
193
|
+
content: {},
|
|
194
|
+
...(record.deletedAt !== undefined && { deletedAt: record.deletedAt }),
|
|
195
|
+
...(record.unlistedAt !== undefined && { unlistedAt: record.unlistedAt }),
|
|
196
|
+
...(record.permissions !== undefined && { permissions: record.permissions }),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** A soft-deleted Record presented as its tombstone; anything else untouched. */
|
|
200
|
+
export const presentDeleted = (record) => record.deletedAt ? tombstoneOf(record) : record;
|
|
201
|
+
//# sourceMappingURL=record-changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-changes.js","sourceRoot":"","sources":["../src/record-changes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record ID and clock-field validation
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The rules a caller-supplied `id`, `parentId`, `createdAt` or `updatedAt`
|
|
5
|
+
* is held to before any of them reaches storage. A client may mint its own
|
|
6
|
+
* id and backdate a record's clock, so both are untrusted input here even
|
|
7
|
+
* when the types say otherwise.
|
|
8
|
+
*
|
|
9
|
+
* See docs/spec/data-model.md § Record IDs.
|
|
10
|
+
*/
|
|
11
|
+
import type { ValidationError } from './validate.js';
|
|
12
|
+
export declare const DEFAULT_ID_TIMESTAMP_SKEW_MS: number;
|
|
13
|
+
/**
|
|
14
|
+
* The same format rule applied to a `parentId` a caller names. Separate
|
|
15
|
+
* from validateRecordId() only for its message: the id under discussion is
|
|
16
|
+
* the destination, not the record being written, and a shared message would
|
|
17
|
+
* report the wrong one. See docs/spec/data-model.md § Reparenting.
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateParentId(parentId: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Format and reserved-prefix checks — full-trust context (Stack.create()).
|
|
22
|
+
* Checked before the format check: the Crockford charset already excludes
|
|
23
|
+
* "_", so a reserved-looking id (e.g. "_config") would otherwise just fail
|
|
24
|
+
* as a generic format error instead of a specific, actionable one.
|
|
25
|
+
*
|
|
26
|
+
* Throws StackQueryError, not StackValidationError: a malformed id is
|
|
27
|
+
* structurally bad input the request never gets past — it doesn't reach
|
|
28
|
+
* type-schema validation — the same reasoning that makes an undecodable
|
|
29
|
+
* pagination cursor a StackQueryError rather than a content-validation
|
|
30
|
+
* failure. See StackQueryError's doc comment.
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateRecordId(id: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Validity and range check for the backdating options on unscoped
|
|
35
|
+
* Stack.create(). A Date is only as good as what the caller parsed it
|
|
36
|
+
* from, and the two failure modes both need catching here rather than
|
|
37
|
+
* downstream:
|
|
38
|
+
*
|
|
39
|
+
* - **Invalid Date** (`new Date('13/45/2020')` off a malformed import row)
|
|
40
|
+
* has a NaN getTime(), and every comparison against NaN is false — so an
|
|
41
|
+
* unchecked Invalid Date passes the updatedAt/createdAt ordering check
|
|
42
|
+
* and the id/createdAt skew check by turning them off, mints the
|
|
43
|
+
* epoch-zero ID `000000000xxx`, and persists a record whose
|
|
44
|
+
* `createdAt.toISOString()` throws RangeError in serializeRecord() —
|
|
45
|
+
* making that record, and any wire response containing it,
|
|
46
|
+
* permanently unreadable.
|
|
47
|
+
* - **Out of encodable range** — before 1970 crockford32Encode() throws a
|
|
48
|
+
* bare RangeError from deep inside the ID encoder, and past
|
|
49
|
+
* MAX_ID_TIMESTAMP the derived ID silently grows to 13 characters and
|
|
50
|
+
* fails isValidIdFormat().
|
|
51
|
+
*
|
|
52
|
+
* Checked at the door for both, as a StackValidationError naming the
|
|
53
|
+
* field. Applies whether or not an `id` is supplied: a clock field outside
|
|
54
|
+
* this range is unrepresentable regardless of where the ID came from.
|
|
55
|
+
*/
|
|
56
|
+
export declare function validateClockField(value: Date | undefined, path: string): ValidationError[];
|
|
57
|
+
/**
|
|
58
|
+
* Timestamp-prefix plausibility check, shared by callers that compare an
|
|
59
|
+
* ID's embedded millisecond against a different reference each:
|
|
60
|
+
* ScopedStack.create() against the current time for any non-backdated
|
|
61
|
+
* create (a grantee is untrusted and could otherwise mint an ID that
|
|
62
|
+
* forges its sort position, and this is the one check standing between a
|
|
63
|
+
* delegated or grantee caller and doing so), and Stack.create() — reached
|
|
64
|
+
* directly when unscoped, or via ScopedStack.create() when the requester
|
|
65
|
+
* is the owner acting alone with an explicit `createdAt` — against that
|
|
66
|
+
* `createdAt` instead (the two must agree, not silently diverge — see
|
|
67
|
+
* docs/spec/data-model.md § Record IDs). Pass null to disable.
|
|
68
|
+
*/
|
|
69
|
+
export declare function validateIdTimestampSkew(id: string, toleranceMs: number | null, referenceMs: number, referenceLabel: string): void;
|
|
70
|
+
//# sourceMappingURL=record-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-id.d.ts","sourceRoot":"","sources":["../src/record-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAOrD,eAAO,MAAM,4BAA4B,QAAsB,CAAC;AAEhE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAWvD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CASjD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CA0B3F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB,IAAI,CAWN"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record ID and clock-field validation
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The rules a caller-supplied `id`, `parentId`, `createdAt` or `updatedAt`
|
|
5
|
+
* is held to before any of them reaches storage. A client may mint its own
|
|
6
|
+
* id and backdate a record's clock, so both are untrusted input here even
|
|
7
|
+
* when the types say otherwise.
|
|
8
|
+
*
|
|
9
|
+
* See docs/spec/data-model.md § Record IDs.
|
|
10
|
+
*/
|
|
11
|
+
import { isValidIdFormat, idTimestamp, MAX_ID_TIMESTAMP } from './id.js';
|
|
12
|
+
import { StackQueryError, StackValidationError } from './errors.js';
|
|
13
|
+
// -------------------------------------------------------
|
|
14
|
+
// Record ID validation
|
|
15
|
+
// -------------------------------------------------------
|
|
16
|
+
const RESERVED_ID_PREFIX = '_';
|
|
17
|
+
export const DEFAULT_ID_TIMESTAMP_SKEW_MS = 24 * 60 * 60 * 1000;
|
|
18
|
+
/**
|
|
19
|
+
* The same format rule applied to a `parentId` a caller names. Separate
|
|
20
|
+
* from validateRecordId() only for its message: the id under discussion is
|
|
21
|
+
* the destination, not the record being written, and a shared message would
|
|
22
|
+
* report the wrong one. See docs/spec/data-model.md § Reparenting.
|
|
23
|
+
*/
|
|
24
|
+
export function validateParentId(parentId) {
|
|
25
|
+
if (parentId.startsWith(RESERVED_ID_PREFIX)) {
|
|
26
|
+
throw new StackQueryError(`Invalid parentId "${parentId}": uses the reserved "${RESERVED_ID_PREFIX}" prefix.`);
|
|
27
|
+
}
|
|
28
|
+
if (!isValidIdFormat(parentId)) {
|
|
29
|
+
throw new StackQueryError(`Invalid parentId "${parentId}": expected 12 lowercase Crockford base-32 characters.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Format and reserved-prefix checks — full-trust context (Stack.create()).
|
|
34
|
+
* Checked before the format check: the Crockford charset already excludes
|
|
35
|
+
* "_", so a reserved-looking id (e.g. "_config") would otherwise just fail
|
|
36
|
+
* as a generic format error instead of a specific, actionable one.
|
|
37
|
+
*
|
|
38
|
+
* Throws StackQueryError, not StackValidationError: a malformed id is
|
|
39
|
+
* structurally bad input the request never gets past — it doesn't reach
|
|
40
|
+
* type-schema validation — the same reasoning that makes an undecodable
|
|
41
|
+
* pagination cursor a StackQueryError rather than a content-validation
|
|
42
|
+
* failure. See StackQueryError's doc comment.
|
|
43
|
+
*/
|
|
44
|
+
export function validateRecordId(id) {
|
|
45
|
+
if (id.startsWith(RESERVED_ID_PREFIX)) {
|
|
46
|
+
throw new StackQueryError(`ID "${id}" uses the reserved "${RESERVED_ID_PREFIX}" prefix.`);
|
|
47
|
+
}
|
|
48
|
+
if (!isValidIdFormat(id)) {
|
|
49
|
+
throw new StackQueryError(`Invalid ID "${id}": expected 12 lowercase Crockford base-32 characters.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Validity and range check for the backdating options on unscoped
|
|
54
|
+
* Stack.create(). A Date is only as good as what the caller parsed it
|
|
55
|
+
* from, and the two failure modes both need catching here rather than
|
|
56
|
+
* downstream:
|
|
57
|
+
*
|
|
58
|
+
* - **Invalid Date** (`new Date('13/45/2020')` off a malformed import row)
|
|
59
|
+
* has a NaN getTime(), and every comparison against NaN is false — so an
|
|
60
|
+
* unchecked Invalid Date passes the updatedAt/createdAt ordering check
|
|
61
|
+
* and the id/createdAt skew check by turning them off, mints the
|
|
62
|
+
* epoch-zero ID `000000000xxx`, and persists a record whose
|
|
63
|
+
* `createdAt.toISOString()` throws RangeError in serializeRecord() —
|
|
64
|
+
* making that record, and any wire response containing it,
|
|
65
|
+
* permanently unreadable.
|
|
66
|
+
* - **Out of encodable range** — before 1970 crockford32Encode() throws a
|
|
67
|
+
* bare RangeError from deep inside the ID encoder, and past
|
|
68
|
+
* MAX_ID_TIMESTAMP the derived ID silently grows to 13 characters and
|
|
69
|
+
* fails isValidIdFormat().
|
|
70
|
+
*
|
|
71
|
+
* Checked at the door for both, as a StackValidationError naming the
|
|
72
|
+
* field. Applies whether or not an `id` is supplied: a clock field outside
|
|
73
|
+
* this range is unrepresentable regardless of where the ID came from.
|
|
74
|
+
*/
|
|
75
|
+
export function validateClockField(value, path) {
|
|
76
|
+
if (value === undefined)
|
|
77
|
+
return [];
|
|
78
|
+
// Type-checked callers always pass a Date, but this option is now
|
|
79
|
+
// reachable from the wire: JSON has no Date, so a server that forwards a
|
|
80
|
+
// parsed `POST /records` body hands us the ISO *string* it deserialized.
|
|
81
|
+
// Without this the very next line is `"2020-…".getTime()` — an unhandled
|
|
82
|
+
// TypeError, a 500 where the caller should have got a 400 naming the
|
|
83
|
+
// field.
|
|
84
|
+
if (!(value instanceof Date)) {
|
|
85
|
+
return [{ path, message: `${path} must be a Date.` }];
|
|
86
|
+
}
|
|
87
|
+
const ms = value.getTime();
|
|
88
|
+
if (Number.isNaN(ms)) {
|
|
89
|
+
return [{ path, message: `${path} is not a valid Date.` }];
|
|
90
|
+
}
|
|
91
|
+
if (ms < 0 || ms > MAX_ID_TIMESTAMP) {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
path,
|
|
95
|
+
message: `${path} is outside the representable range ` +
|
|
96
|
+
`(1970-01-01T00:00:00.000Z…${new Date(MAX_ID_TIMESTAMP).toISOString()}).`,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Timestamp-prefix plausibility check, shared by callers that compare an
|
|
104
|
+
* ID's embedded millisecond against a different reference each:
|
|
105
|
+
* ScopedStack.create() against the current time for any non-backdated
|
|
106
|
+
* create (a grantee is untrusted and could otherwise mint an ID that
|
|
107
|
+
* forges its sort position, and this is the one check standing between a
|
|
108
|
+
* delegated or grantee caller and doing so), and Stack.create() — reached
|
|
109
|
+
* directly when unscoped, or via ScopedStack.create() when the requester
|
|
110
|
+
* is the owner acting alone with an explicit `createdAt` — against that
|
|
111
|
+
* `createdAt` instead (the two must agree, not silently diverge — see
|
|
112
|
+
* docs/spec/data-model.md § Record IDs). Pass null to disable.
|
|
113
|
+
*/
|
|
114
|
+
export function validateIdTimestampSkew(id, toleranceMs, referenceMs, referenceLabel) {
|
|
115
|
+
if (toleranceMs === null)
|
|
116
|
+
return;
|
|
117
|
+
const skew = Math.abs(referenceMs - idTimestamp(id));
|
|
118
|
+
if (skew > toleranceMs) {
|
|
119
|
+
throw new StackValidationError([
|
|
120
|
+
{
|
|
121
|
+
path: 'id',
|
|
122
|
+
message: `ID "${id}" timestamp disagrees with ${referenceLabel} by more than the allowed clock-skew tolerance (${toleranceMs}ms).`,
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=record-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-id.js","sourceRoot":"","sources":["../src/record-id.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA6B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA6B,MAAM,YAAY,CAAC;AAmExE;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAU,QAAQ,UAAU,KAAG,OAAO,CAAC,MAAM,CAMnE,CAAC;AA2EF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,iBAAiB,UAAU,EAAE,gBAAgB,UAAU,KAAG,OAC9B,CAAC;AAU1D,MAAM,MAAM,oBAAoB,GAAG;IACjC,qHAAqH;IACrH,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAwFF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,UAAU,EAAE,WAAW,UAAU,KAAG,oBAAoB,EAI3F,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,KAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAIlF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,KAAG,MAAgC,CAAC;AAE/F;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,KAAG,MAA+C,CAAC;AAE1F;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,KAAG,OAGnD,CAAC"}
|
package/dist/schema.js
CHANGED
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
*/
|
|
18
18
|
const canonicalizeFieldDef = (def) => {
|
|
19
19
|
if (def.kind === 'array') {
|
|
20
|
+
// `open` carries into the hash and `items` drops out of it, for the
|
|
21
|
+
// reason an object's `properties` does below: an open array accepts
|
|
22
|
+
// content a declared one refuses. A closed array omits the flag rather
|
|
23
|
+
// than storing `false`, so declaring it explicitly hashes the same as
|
|
24
|
+
// leaving it off.
|
|
25
|
+
if (def.open) {
|
|
26
|
+
return {
|
|
27
|
+
kind: def.kind,
|
|
28
|
+
open: true,
|
|
29
|
+
...(def.required !== undefined && { required: def.required }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
20
32
|
return {
|
|
21
33
|
items: canonicalizeFieldDef(def.items),
|
|
22
34
|
kind: def.kind,
|
|
@@ -24,6 +36,15 @@ const canonicalizeFieldDef = (def) => {
|
|
|
24
36
|
};
|
|
25
37
|
}
|
|
26
38
|
if (def.kind === 'object') {
|
|
39
|
+
// An open object and one declaring no properties accept different
|
|
40
|
+
// content, so they must not share a hash.
|
|
41
|
+
if (def.open) {
|
|
42
|
+
return {
|
|
43
|
+
kind: def.kind,
|
|
44
|
+
open: true,
|
|
45
|
+
...(def.required !== undefined && { required: def.required }),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
27
48
|
return {
|
|
28
49
|
kind: def.kind,
|
|
29
50
|
properties: canonicalizeSchema(def.properties),
|
|
@@ -78,6 +99,8 @@ const READ_COMPATIBLE = {
|
|
|
78
99
|
// items / object properties is depth-bounded — matches MAX_VALIDATION_DEPTH
|
|
79
100
|
// in validate.ts. Past the limit we can't verify compatibility, so we
|
|
80
101
|
// fail closed (treat as incompatible) rather than risk a stack overflow.
|
|
102
|
+
/** Only a container can be open, so this is false for every scalar. */
|
|
103
|
+
const isOpen = (def) => (def.kind === 'array' || def.kind === 'object') && def.open === true;
|
|
81
104
|
const MAX_COMPATIBILITY_DEPTH = 32;
|
|
82
105
|
/**
|
|
83
106
|
* Check whether a candidate field satisfies a required field, recursing
|
|
@@ -87,11 +110,24 @@ const isFieldCompatible = (candidate, required, depth) => {
|
|
|
87
110
|
if (depth > MAX_COMPATIBILITY_DEPTH)
|
|
88
111
|
return false;
|
|
89
112
|
if (required.kind === 'array') {
|
|
90
|
-
|
|
113
|
+
if (candidate.kind !== 'array')
|
|
114
|
+
return false;
|
|
115
|
+
// An open requirement asks nothing of the elements, so any array
|
|
116
|
+
// satisfies it; a declared one is unsatisfied by an open candidate,
|
|
117
|
+
// which promises nothing about what it holds.
|
|
118
|
+
if (required.open)
|
|
119
|
+
return true;
|
|
120
|
+
return !candidate.open && isFieldCompatible(candidate.items, required.items, depth + 1);
|
|
91
121
|
}
|
|
92
122
|
if (required.kind === 'object') {
|
|
93
|
-
|
|
94
|
-
|
|
123
|
+
if (candidate.kind !== 'object')
|
|
124
|
+
return false;
|
|
125
|
+
// Same on both sides: an open candidate declares no fields, so it
|
|
126
|
+
// satisfies a required object only where that one asks for none — a bag
|
|
127
|
+
// promises a consumer nothing to read.
|
|
128
|
+
if (required.open)
|
|
129
|
+
return true;
|
|
130
|
+
return isCompatibleAtDepth(candidate.open ? {} : candidate.properties, required.properties, depth + 1);
|
|
95
131
|
}
|
|
96
132
|
if (candidate.kind === 'array' || candidate.kind === 'object')
|
|
97
133
|
return false;
|
|
@@ -138,11 +174,26 @@ const diffField = (path, stored, candidate, depth, violations) => {
|
|
|
138
174
|
message: `required changed from ${!!stored.required} to ${!!candidate.required}`,
|
|
139
175
|
});
|
|
140
176
|
}
|
|
177
|
+
// Opening a declared container, or closing an open one, changes which
|
|
178
|
+
// content it accepts in a way no field-by-field diff would show.
|
|
179
|
+
if (isOpen(stored) !== isOpen(candidate)) {
|
|
180
|
+
violations.push({
|
|
181
|
+
path,
|
|
182
|
+
message: isOpen(stored)
|
|
183
|
+
? `${stored.kind} changed from open to declared`
|
|
184
|
+
: `${stored.kind} changed from declared to open`,
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
141
188
|
if (stored.kind === 'array' && candidate.kind === 'array') {
|
|
142
|
-
|
|
189
|
+
if (!stored.open && !candidate.open) {
|
|
190
|
+
diffField(`${path}[]`, stored.items, candidate.items, depth + 1, violations);
|
|
191
|
+
}
|
|
143
192
|
}
|
|
144
193
|
if (stored.kind === 'object' && candidate.kind === 'object') {
|
|
145
|
-
|
|
194
|
+
if (!stored.open && !candidate.open) {
|
|
195
|
+
diffFields(path, stored.properties, candidate.properties, depth + 1, violations);
|
|
196
|
+
}
|
|
146
197
|
}
|
|
147
198
|
};
|
|
148
199
|
const diffFields = (prefix, stored, candidate, depth, violations) => {
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,0DAA0D;AAC1D,iCAAiC;AACjC,0DAA0D;AAE1D;;;GAGG;AACH,MAAM,oBAAoB,GAAG,CAAC,GAAa,EAAW,EAAE;IACtD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzB,oEAAoE;QACpE,oEAAoE;QACpE,uEAAuE;QACvE,sEAAsE;QACtE,kBAAkB;QAClB,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YACb,OAAO;gBACL,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,IAAI,EAAE,IAAI;gBACV,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;aAC9D,CAAC;QACJ,CAAC;QACD,OAAO;YACL,KAAK,EAAE,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC;YACtC,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;SAC9D,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC1B,kEAAkE;QAClE,0CAA0C;QAC1C,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YACb,OAAO;gBACL,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,IAAI,EAAE,IAAI;gBACV,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;aAC9D,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,UAAU,EAAE,kBAAkB,CAAC,GAAG,CAAC,UAAU,CAAC;YAC9C,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;SAC9D,CAAC;IACJ,CAAC;IAED,SAAS;IACT,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;KAC9D,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,MAAkB,EAAW,EAAE;IACzD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;SAChB,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,oBAAoB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAC1D,CAAC;AACJ,CAAC,CAAC;AAEF,0DAA0D;AAC1D,UAAU;AACV,0DAA0D;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,MAAkB,EAAmB,EAAE;IACtE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IACzD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxE,CAAC,CAAC;AAEF,0DAA0D;AAC1D,qBAAqB;AACrB,0DAA0D;AAE1D;;;;GAIG;AACH,MAAM,eAAe,GAA+C;IAClE,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;IACxB,MAAM,EAAE,CAAC,QAAQ,CAAC;IAClB,OAAO,EAAE,CAAC,SAAS,CAAC;IACpB,IAAI,EAAE,CAAC,MAAM,CAAC;IACd,YAAY,EAAE,CAAC,YAAY,CAAC;IAC5B,UAAU,EAAE,CAAC,UAAU,CAAC;CACzB,CAAC;AAEF,qEAAqE;AACrE,qEAAqE;AACrE,4EAA4E;AAC5E,sEAAsE;AACtE,yEAAyE;AACzE,uEAAuE;AACvE,MAAM,MAAM,GAAG,CAAC,GAAa,EAAW,EAAE,CACxC,CAAC,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC;AAEvE,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAEnC;;;GAGG;AACH,MAAM,iBAAiB,GAAG,CAAC,SAAmB,EAAE,QAAkB,EAAE,KAAa,EAAW,EAAE;IAC5F,IAAI,KAAK,GAAG,uBAAuB;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC9B,IAAI,SAAS,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,KAAK,CAAC;QAC7C,iEAAiE;QACjE,oEAAoE;QACpE,8CAA8C;QAC9C,IAAI,QAAQ,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAC/B,OAAO,CAAC,SAAS,CAAC,IAAI,IAAI,iBAAiB,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IAC1F,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC9C,kEAAkE;QAClE,wEAAwE;QACxE,uCAAuC;QACvC,IAAI,QAAQ,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAC/B,OAAO,mBAAmB,CACxB,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,EAC1C,QAAQ,CAAC,UAAU,EACnB,KAAK,GAAG,CAAC,CACV,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,KAAK,OAAO,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5E,OAAO,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAC1B,eAA2B,EAC3B,cAA0B,EAC1B,KAAa,EACJ,EAAE;IACX,IAAI,KAAK,GAAG,uBAAuB;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE;QACzD,IAAI,CAAC,GAAG,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QACnC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAChG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,eAA2B,EAAE,cAA0B,EAAW,EAAE,CAC/F,mBAAmB,CAAC,eAAe,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;AAgB1D,yEAAyE;AACzE,uEAAuE;AACvE,wEAAwE;AACxE,+BAA+B;AAC/B,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,MAAM,SAAS,GAAG,CAChB,IAAY,EACZ,MAAgB,EAChB,SAAmB,EACnB,KAAa,EACb,UAAkC,EAC5B,EAAE;IACR,IAAI,KAAK,GAAG,cAAc,EAAE,CAAC;QAC3B,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,0DAA0D,EAAE,CAAC,CAAC;QAC/F,OAAO;IACT,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;QACnC,UAAU,CAAC,IAAI,CAAC;YACd,IAAI;YACJ,OAAO,EAAE,sBAAsB,MAAM,CAAC,IAAI,SAAS,SAAS,CAAC,IAAI,GAAG;SACrE,CAAC,CAAC;QACH,OAAO,CAAC,qEAAqE;IAC/E,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC/C,UAAU,CAAC,IAAI,CAAC;YACd,IAAI;YACJ,OAAO,EAAE,yBAAyB,CAAC,CAAC,MAAM,CAAC,QAAQ,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,EAAE;SACjF,CAAC,CAAC;IACL,CAAC;IACD,sEAAsE;IACtE,iEAAiE;IACjE,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,UAAU,CAAC,IAAI,CAAC;YACd,IAAI;YACJ,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC;gBACrB,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,gCAAgC;gBAChD,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,gCAAgC;SACnD,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,SAAS,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC1D,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;YACpC,SAAS,CAAC,GAAG,IAAI,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5D,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;YACpC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,UAAU,EAAE,KAAK,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CACjB,MAAc,EACd,MAAkB,EAClB,SAAqB,EACrB,KAAa,EACb,UAAkC,EAC5B,EAAE;IACR,IAAI,KAAK,GAAG,cAAc,EAAE,CAAC;QAC3B,UAAU,CAAC,IAAI,CAAC;YACd,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,0DAA0D;SACpE,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAC/C,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;YACpD,SAAS;QACX,CAAC;QACD,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5D,IAAI,GAAG,IAAI,MAAM;YAAE,SAAS,CAAC,yBAAyB;QACtD,IAAI,YAAY,CAAC,QAAQ,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;YAC/C,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC,CAAC;QAC3F,CAAC;QACD,iFAAiF;IACnF,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAkB,EAAE,SAAqB,EAA0B,EAAE;IAC/F,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;IACjD,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC;AAEF,0DAA0D;AAC1D,kBAAkB;AAClB,0DAA0D;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAc,EAA8C,EAAE;IACxF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AAC/D,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,OAAe,EAAU,EAAE,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC;AAE/F;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,MAAM,CAAC;AAE1F;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAc,EAAW,EAAE;IAC5D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;AAC/D,CAAC,CAAC"}
|