@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
package/dist/stack.d.ts
CHANGED
|
@@ -13,19 +13,9 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Apps should never talk to a StackAdapter directly.
|
|
15
15
|
*/
|
|
16
|
-
import type {
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import type { StackRecord, StackType, TypeSchema, TypeId, StackAdapter, StackQuery, QuerySort, RecordFilter, QueryResult, Association, RelationshipTarget, Permission, Migration, RecordVersion, StackFeatures, GrantAction, AttachmentContent, FileId, EntityId, AppId, RecordId, TokenSession, ActorOptions, RecordChange, SubscribeOptions, Unsubscribe } from './types.js';
|
|
20
|
-
/**
|
|
21
|
-
* Who a grant() / revoke() / listGrants() call targets: a specific entity
|
|
22
|
-
* (DID), a `_group` Record's roster (by ID), or `null` for the default
|
|
23
|
-
* grant / default-only listing. See docs/spec/access-control.md § Type-level
|
|
24
|
-
* grants.
|
|
25
|
-
*/
|
|
26
|
-
export type GrantTarget = EntityId | {
|
|
27
|
-
groupId: RecordId;
|
|
28
|
-
} | null;
|
|
16
|
+
import type { StackRecord, StackType, TypeSchema, TypeId, StackAdapter, StackQuery, QueryResult, Association, Permission, Migration, RecordVersion, StackFeatures, GrantAction, AttachmentContent, FileId, EntityId, AppId, TokenSession, ActorOptions, RecordChange, RecordChanges, SubscribeOptions, Unsubscribe } from './types.js';
|
|
17
|
+
import type { GrantTarget } from './grants.js';
|
|
18
|
+
import { ScopedStack } from './scoped-stack.js';
|
|
29
19
|
export type CreateRecordOptions = {
|
|
30
20
|
/**
|
|
31
21
|
* Client-minted record ID. Must be 12 lowercase Crockford base-32
|
|
@@ -49,7 +39,7 @@ export type CreateRecordOptions = {
|
|
|
49
39
|
/**
|
|
50
40
|
* Create the record already unlisted, so the create event itself is
|
|
51
41
|
* withheld from the feed — there is no window where the record exists
|
|
52
|
-
* and is listed before
|
|
42
|
+
* and is listed before a mutation catches up. See
|
|
53
43
|
* docs/spec/unlisted.md.
|
|
54
44
|
*/
|
|
55
45
|
unlisted?: boolean;
|
|
@@ -151,204 +141,6 @@ export type DeleteRecordOptions = IfVersionOptions & {
|
|
|
151
141
|
export type DefineTypeOptions = {
|
|
152
142
|
migratesFrom?: TypeId;
|
|
153
143
|
};
|
|
154
|
-
/**
|
|
155
|
-
* The wire-protocol discriminator vocabulary, one code per Stack-domain
|
|
156
|
-
* error class. Lives here rather than in @haverstack/wire-types because the
|
|
157
|
-
* classes that carry these codes are defined here; wire-types re-exports it
|
|
158
|
-
* as WireErrorCode. See docs/spec/wire-format.md § Wire error body.
|
|
159
|
-
*/
|
|
160
|
-
export type StackErrorCode = 'bad_request' | 'permission' | 'not_found' | 'conflict' | 'version_conflict' | 'validation' | 'migration' | 'schema_drift' | 'payload_too_large' | 'timeout';
|
|
161
|
-
/**
|
|
162
|
-
* Root of the Stack error taxonomy. A single `instanceof StackError` answers
|
|
163
|
-
* "is this a Stack-domain error or a bug?" — the question a server's error
|
|
164
|
-
* middleware asks before serializing a wire body, and one a nine-arm
|
|
165
|
-
* instanceof ladder answers only by exhaustion. Every subclass carries its
|
|
166
|
-
* discriminator as an instance `code`, so serialization is a lookup rather
|
|
167
|
-
* than a chain of class tests.
|
|
168
|
-
*
|
|
169
|
-
* Membership implies a wire mapping: every code has an entry in
|
|
170
|
-
* WIRE_ERROR_STATUS. Errors with no wire representation (IdGenerationError,
|
|
171
|
-
* InvalidDidError) deliberately stay outside this hierarchy.
|
|
172
|
-
*
|
|
173
|
-
* Subclassing adds no hierarchy beyond this root — notably
|
|
174
|
-
* StackVersionConflictError is a sibling of StackConflictError, not a
|
|
175
|
-
* subtype. See docs/spec/wire-format.md § Error responses.
|
|
176
|
-
*/
|
|
177
|
-
export declare abstract class StackError extends Error {
|
|
178
|
-
abstract readonly code: StackErrorCode;
|
|
179
|
-
}
|
|
180
|
-
export declare class StackValidationError extends StackError {
|
|
181
|
-
readonly errors: ValidationError[];
|
|
182
|
-
static readonly code: "validation";
|
|
183
|
-
readonly code: "validation";
|
|
184
|
-
constructor(errors: ValidationError[]);
|
|
185
|
-
}
|
|
186
|
-
export declare class StackMigrationError extends StackError {
|
|
187
|
-
static readonly code: "migration";
|
|
188
|
-
readonly code: "migration";
|
|
189
|
-
constructor(message: string);
|
|
190
|
-
}
|
|
191
|
-
/** Thrown by ScopedStack when a requester lacks permission for the operation. */
|
|
192
|
-
export declare class StackPermissionError extends StackError {
|
|
193
|
-
static readonly code: "permission";
|
|
194
|
-
readonly code: "permission";
|
|
195
|
-
constructor(message?: string);
|
|
196
|
-
}
|
|
197
|
-
/** Thrown when a record (or specific version) does not exist. */
|
|
198
|
-
export declare class StackNotFoundError extends StackError {
|
|
199
|
-
static readonly code: "not_found";
|
|
200
|
-
readonly code: "not_found";
|
|
201
|
-
constructor(message: string);
|
|
202
|
-
}
|
|
203
|
-
/** Thrown when an operation cannot proceed due to a constraint violation (e.g. deleting an attachment that is still referenced). */
|
|
204
|
-
export declare class StackConflictError extends StackError {
|
|
205
|
-
static readonly code: "conflict";
|
|
206
|
-
readonly code: "conflict";
|
|
207
|
-
constructor(message: string);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Thrown when an `ifVersion` precondition doesn't match a record's current
|
|
211
|
-
* version. Deliberately not a StackConflictError subtype — the two have
|
|
212
|
-
* different recovery stories and HTTP statuses (409 vs. 412). See
|
|
213
|
-
* docs/spec/versioning.md § Optimistic concurrency (`ifVersion`).
|
|
214
|
-
*/
|
|
215
|
-
export declare class StackVersionConflictError extends StackError {
|
|
216
|
-
readonly recordId: string;
|
|
217
|
-
readonly expectedVersion: number;
|
|
218
|
-
readonly actualVersion: number;
|
|
219
|
-
static readonly code: "version_conflict";
|
|
220
|
-
readonly code: "version_conflict";
|
|
221
|
-
constructor(message: string, recordId: string, expectedVersion: number, actualVersion: number);
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Thrown when a request is structurally malformed — not a content-validation
|
|
225
|
-
* failure, but input the adapter/server can't even interpret (e.g. an
|
|
226
|
-
* undecodable pagination cursor, a malformed TypeId, search text the engine
|
|
227
|
-
* cannot parse, or a typeId no definition exists for). Distinct from
|
|
228
|
-
* StackValidationError, which means the request was well-formed but content
|
|
229
|
-
* failed schema validation.
|
|
230
|
-
*
|
|
231
|
-
* Naming something absent belongs here rather than under `not_found`: a
|
|
232
|
-
* request whose *type* is undefined never addressed a record, so answering
|
|
233
|
-
* 404 would say a record was missing when none was asked for.
|
|
234
|
-
*/
|
|
235
|
-
export declare class StackQueryError extends StackError {
|
|
236
|
-
static readonly code: "bad_request";
|
|
237
|
-
readonly code: "bad_request";
|
|
238
|
-
constructor(message: string);
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Fail loud rather than silently widen: a filter the adapter can't honor
|
|
242
|
-
* would otherwise be dropped, returning an unfiltered superset presented
|
|
243
|
-
* as the filtered result. Shared by Stack.query() and
|
|
244
|
-
* APIAdapter.queryRecords(). See docs/spec/data-model.md
|
|
245
|
-
* § Capability-gated filters.
|
|
246
|
-
*/
|
|
247
|
-
export declare function assertQueryCapabilities(filter: RecordFilter | undefined, capabilities: Pick<StackFeatures, 'filter'>): void;
|
|
248
|
-
/**
|
|
249
|
-
* Split a content filter key into path segments. Write-time validation
|
|
250
|
-
* keeps stored field names free of the path metacharacters, but a filter
|
|
251
|
-
* arrives from a request body and has made no such promise, so the same
|
|
252
|
-
* rule is enforced here. See docs/spec/data-model.md § Content field names.
|
|
253
|
-
*/
|
|
254
|
-
export declare function parseContentFilterKey(key: string): string[];
|
|
255
|
-
/**
|
|
256
|
-
* Reject a sort whose field or direction is outside the closed set the
|
|
257
|
-
* types promise. `QuerySort` is typed `'asc' | 'desc'`, but a type is not a
|
|
258
|
-
* runtime guard: a server mapping `?direction=` onto a query, or a
|
|
259
|
-
* delegated app calling query(), supplies a raw string. A SQLite record
|
|
260
|
-
* adapter interpolates the direction straight into `ORDER BY`, so an
|
|
261
|
-
* unvalidated value there is a SQL-injection sink reachable from every
|
|
262
|
-
* untrusted caller. Validating in the invariant layer — the same reason
|
|
263
|
-
* emission and _config protection live here — means no adapter can forget
|
|
264
|
-
* it. See docs/spec/data-model.md § Sorting and pagination.
|
|
265
|
-
*/
|
|
266
|
-
export declare function assertValidSort(sort: QuerySort | undefined): void;
|
|
267
|
-
/**
|
|
268
|
-
* Fail loud rather than silently reorder: an adapter that can't honor the
|
|
269
|
-
* requested sort would otherwise answer in some other order, which a
|
|
270
|
-
* caller paging a bounded window has no way to notice. The companion to
|
|
271
|
-
* assertQueryCapabilities(), split from it because a sort is not a filter
|
|
272
|
-
* — see docs/spec/data-model.md § Capability-gated filters.
|
|
273
|
-
*/
|
|
274
|
-
export declare function assertSortCapability(sort: QuerySort | undefined, capabilities: Pick<StackFeatures, 'sort'>): void;
|
|
275
|
-
/**
|
|
276
|
-
* Reject a relationship filter that names neither a label nor a target,
|
|
277
|
-
* or whose target is malformed. `RelatedToFilter` promises one half is
|
|
278
|
-
* always present; without the runtime check a filter decoded from a
|
|
279
|
-
* request could arrive empty and match every record carrying any
|
|
280
|
-
* relationship. See docs/spec/data-model.md § Filter.
|
|
281
|
-
*/
|
|
282
|
-
export declare function assertValidRelatedTo(relatedTo: RecordFilter['relatedTo']): void;
|
|
283
|
-
/**
|
|
284
|
-
* Thrown when an attachment upload exceeds the adapter's declared
|
|
285
|
-
* `limits.attachmentBytes` ceiling — checked client-side before any bytes
|
|
286
|
-
* are sent; a server still enforces 413 authoritatively regardless. See
|
|
287
|
-
* docs/spec/wire-format.md § Attachments.
|
|
288
|
-
*/
|
|
289
|
-
export declare class StackPayloadTooLargeError extends StackError {
|
|
290
|
-
static readonly code: "payload_too_large";
|
|
291
|
-
readonly code: "payload_too_large";
|
|
292
|
-
constructor(message: string);
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Thrown when a server abandons an operation for taking too long — in
|
|
296
|
-
* practice a full-text search, the one query whose cost the sanitizers
|
|
297
|
-
* bound the *grammar* of but not the execution of (see
|
|
298
|
-
* docs/spec/data-model.md § Capability-gated filters).
|
|
299
|
-
*
|
|
300
|
-
* Never produced in-process: both SQLite engines run synchronously, so
|
|
301
|
-
* there is nothing to interrupt from inside the call. It exists so a
|
|
302
|
-
* server that bounds query time has a class to serialize, and so the app
|
|
303
|
-
* catching it can tell "too expensive, narrow it and retry" from
|
|
304
|
-
* StackQueryError's "malformed, don't bother retrying" — the distinction
|
|
305
|
-
* that would be lost if a timeout reused `bad_request`.
|
|
306
|
-
*/
|
|
307
|
-
export declare class StackTimeoutError extends StackError {
|
|
308
|
-
static readonly code: "timeout";
|
|
309
|
-
readonly code: "timeout";
|
|
310
|
-
constructor(message: string);
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Thrown by defineType() when redefining an existing typeId with a schema
|
|
314
|
-
* change beyond additive evolution. The remedy is a new version, never an
|
|
315
|
-
* in-place redefinition. See docs/spec/data-model.md § Schema drift
|
|
316
|
-
* detection.
|
|
317
|
-
*/
|
|
318
|
-
export declare class StackSchemaDriftError extends StackError {
|
|
319
|
-
readonly typeId: TypeId;
|
|
320
|
-
readonly violations: SchemaDriftViolation[];
|
|
321
|
-
static readonly code: "schema_drift";
|
|
322
|
-
readonly code: "schema_drift";
|
|
323
|
-
constructor(typeId: TypeId, violations: SchemaDriftViolation[]);
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Thrown when a Stack or ScopedStack is used after close(). Deliberately
|
|
327
|
-
* outside the StackError taxonomy, alongside IdGenerationError and
|
|
328
|
-
* InvalidDidError: a caller holding a closed client is a local programming
|
|
329
|
-
* error with no wire representation — no server ever responds with it.
|
|
330
|
-
* See docs/spec/adapters.md § Lifecycle.
|
|
331
|
-
*/
|
|
332
|
-
export declare class StackClosedError extends Error {
|
|
333
|
-
constructor(message?: string);
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Thrown when a scoped view is asked to observe a stack whose adapter
|
|
337
|
-
* relays a remote feed. Outside the StackError taxonomy for the same
|
|
338
|
-
* reason StackClosedError is: it reports a topology the caller assembled,
|
|
339
|
-
* not a state a request can be in.
|
|
340
|
-
*
|
|
341
|
-
* A relayed frame is scoped by the authority that opened the feed, and a
|
|
342
|
-
* narrower scope cannot re-derive that decision — a purge leaves no record
|
|
343
|
-
* to check `canRead` against. Delivering anyway would break the promise
|
|
344
|
-
* that a subscriber never sees what it may not read; delivering only local
|
|
345
|
-
* writes would silently drop every change made elsewhere, which is the
|
|
346
|
-
* failure that looks fine in testing. So it refuses.
|
|
347
|
-
* See docs/spec/events.md § Permission scoping.
|
|
348
|
-
*/
|
|
349
|
-
export declare class StackRelayScopeError extends Error {
|
|
350
|
-
constructor(message: string);
|
|
351
|
-
}
|
|
352
144
|
/**
|
|
353
145
|
* The app-facing record API, implemented by both Stack and ScopedStack.
|
|
354
146
|
* Accept this type in plugin or extension code to remain adapter-agnostic
|
|
@@ -371,18 +163,20 @@ export interface StackClient {
|
|
|
371
163
|
getEntityByDid(did: EntityId): Promise<StackRecord | null>;
|
|
372
164
|
/** getEntityByDid() for the one DID every stack reserves: its own owner. */
|
|
373
165
|
getOwnerEntity(): Promise<StackRecord | null>;
|
|
374
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Apply a change set: any combination of content patch, `parentId`,
|
|
168
|
+
* `permissions`, `associations` and `unlisted`, in one atomic write
|
|
169
|
+
* producing one version. Keys are read for presence, so `unlisted: false`
|
|
170
|
+
* and `parentId: null` are changes; a change set naming no key at all is
|
|
171
|
+
* a StackQueryError. Under ScopedStack each key carries its own gate and
|
|
172
|
+
* one refused key refuses the call.
|
|
173
|
+
* See docs/spec/data-model.md § Mutations.
|
|
174
|
+
*/
|
|
175
|
+
mutate(id: string, changes: RecordChanges, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
176
|
+
/** mutate() with `contentPatch` alone — the common case, named for it. */
|
|
177
|
+
patchContent(id: string, patch: Record<string, unknown | null>, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
375
178
|
associate(id: string, association: Association, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
376
179
|
dissociate(id: string, association: Association, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
377
|
-
setPermissions(id: string, permissions: Permission[], opts?: IfVersionOptions): Promise<StackRecord>;
|
|
378
|
-
setUnlisted(id: string, unlisted: boolean, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
379
|
-
/**
|
|
380
|
-
* Move a record into another container, or to the root with `null`.
|
|
381
|
-
* The only native field a write-holder may change after creation; it
|
|
382
|
-
* decides enumeration, never access. See docs/spec/data-model.md
|
|
383
|
-
* § Reparenting.
|
|
384
|
-
*/
|
|
385
|
-
setParent(id: string, parentId: string | null, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
386
180
|
delete(id: string, opts?: DeleteRecordOptions): Promise<void>;
|
|
387
181
|
undelete(id: string, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
388
182
|
getVersions(id: string): Promise<RecordVersion[]>;
|
|
@@ -434,7 +228,7 @@ export declare class Stack implements StackClient {
|
|
|
434
228
|
/**
|
|
435
229
|
* Announce a mutation that has already been persisted. Called after the
|
|
436
230
|
* adapter write resolves and before the mutating method settles, so
|
|
437
|
-
* `await stack.
|
|
231
|
+
* `await stack.mutate(...)` guarantees subscribers have been notified —
|
|
438
232
|
* and nothing about work they deferred. A handler cannot fail the write:
|
|
439
233
|
* there is nothing left to fail. See docs/spec/events.md § Handlers.
|
|
440
234
|
*/
|
|
@@ -563,16 +357,62 @@ export declare class Stack implements StackClient {
|
|
|
563
357
|
*/
|
|
564
358
|
get(id: string, opts?: GetRecordOptions): Promise<StackRecord | null>;
|
|
565
359
|
/**
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
360
|
+
* Apply a change set — content patch, `parentId`, `permissions`,
|
|
361
|
+
* `associations`, `unlisted`, in any combination — as one atomic write
|
|
362
|
+
* producing one version and one snapshot. Keys are read for presence, so
|
|
363
|
+
* `unlisted: false` and `parentId: null` are changes.
|
|
364
|
+
*
|
|
365
|
+
* Every key present is checked against the record as it stands, and a
|
|
366
|
+
* change set already satisfied in all of them writes nothing and returns
|
|
367
|
+
* the record unchanged. The ops the change event carries are derived
|
|
368
|
+
* from that same comparison, so naming an aspect without moving it is
|
|
369
|
+
* never reported as moving it.
|
|
370
|
+
*
|
|
371
|
+
* Content is validated against the record's *current* stored type and
|
|
372
|
+
* `typeId` never changes (see docs/spec/data-model.md § Type
|
|
373
|
+
* migrations). See docs/spec/data-model.md § Mutations.
|
|
374
|
+
*/
|
|
375
|
+
mutate(id: string, changes: RecordChanges, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
|
|
376
|
+
/**
|
|
377
|
+
* mutate() with `contentPatch` alone. The common case by a wide margin,
|
|
378
|
+
* and named for what it does rather than for a symmetry with create()
|
|
379
|
+
* that a patch does not have.
|
|
380
|
+
*/
|
|
381
|
+
patchContent(id: string, patch: Record<string, unknown | null>, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
|
|
382
|
+
/**
|
|
383
|
+
* Every check a change set owes before anything is written, in one pass:
|
|
384
|
+
* one refused key refuses the call, so none of them may run after a
|
|
385
|
+
* partial write. Returns the merged content when the set carries a
|
|
386
|
+
* content patch, since the caller needs it to decide whether content
|
|
387
|
+
* actually moved.
|
|
388
|
+
*
|
|
389
|
+
* Parent existence and acyclicity are asked only when the destination
|
|
390
|
+
* differs from where the record already sits — a move to where it
|
|
391
|
+
* already is names no new edge, and would otherwise cost a read apiece.
|
|
571
392
|
*/
|
|
572
|
-
|
|
393
|
+
private validateChangeSet;
|
|
394
|
+
/**
|
|
395
|
+
* Refuse a write that would leave a `_group` Record with no `admin` on
|
|
396
|
+
* its roster. Asked of the roster the write would *produce* — the one
|
|
397
|
+
* place a change-set check reads the post-state rather than the
|
|
398
|
+
* pre-state, because the rule is about what is left behind rather than
|
|
399
|
+
* what the caller named.
|
|
400
|
+
*
|
|
401
|
+
* That framing is the whole of the self-removal question: an `admin`
|
|
402
|
+
* removing themselves passes while another remains and is refused when
|
|
403
|
+
* they are the last, and neither case needs to know whose entry went.
|
|
404
|
+
*
|
|
405
|
+
* Lives here rather than in `ScopedStack` deliberately. Who may write a
|
|
406
|
+
* `_group` is a permission question and belongs to the gate up there;
|
|
407
|
+
* this is an integrity constraint on the Record, so it binds every
|
|
408
|
+
* requester — the stack owner included, who would otherwise be the hole
|
|
409
|
+
* that stops anything downstream relying on the invariant.
|
|
410
|
+
* See docs/spec/identity.md § Group.
|
|
411
|
+
*/
|
|
412
|
+
private assertGroupAdminRemains;
|
|
573
413
|
/**
|
|
574
414
|
* Add an association to a record. Snapshots the record's prior state and
|
|
575
|
-
* bumps version, same as
|
|
415
|
+
* bumps version, same as patchContent() — associations are covered by the same
|
|
576
416
|
* versioning rule as content.
|
|
577
417
|
* If the association already exists (same kind, label, and payload), this
|
|
578
418
|
* is a no-op. Returns the record as it now stands — unchanged on a no-op.
|
|
@@ -585,43 +425,21 @@ export declare class Stack implements StackClient {
|
|
|
585
425
|
*/
|
|
586
426
|
dissociate(id: string, association: Association, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
|
|
587
427
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* Withhold a record from enumeration, or restore it. Orthogonal to
|
|
596
|
-
* setPermissions(): it says nothing about who may read the record, only
|
|
597
|
-
* whether `query()` and the change feed enumerate it by default. No-op if
|
|
598
|
-
* already in the requested state. Returns the record as it now stands —
|
|
599
|
-
* unchanged on a no-op. See docs/spec/access-control.md §
|
|
600
|
-
* Unlisted records.
|
|
601
|
-
*
|
|
602
|
-
* The op passed to emitChange() carries the transition direction —
|
|
603
|
-
* `unlist` (kind `deleted`, so subscribers already holding the record are
|
|
604
|
-
* told to drop it) or `list` (kind `changed`, an upsert like `undelete`,
|
|
605
|
-
* for the record's publish moment).
|
|
606
|
-
*/
|
|
607
|
-
setUnlisted(id: string, unlisted: boolean, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
|
|
608
|
-
/**
|
|
609
|
-
* Move a record into another container, or to the root with `null`.
|
|
610
|
-
* Snapshots and bumps version, same as setUnlisted(). No-op if the
|
|
611
|
-
* record is already there. Returns the record as it now stands —
|
|
612
|
-
* unchanged on a no-op.
|
|
428
|
+
* The checks a *caller-named* destination owes, before the cycle walk:
|
|
429
|
+
* `parentId` is a real, well-formed record id. Format first, so a
|
|
430
|
+
* malformed one is a 400 naming the problem rather than a read that
|
|
431
|
+
* cannot match. Existence closes the gap between the owner path and a
|
|
432
|
+
* non-owner's, where canReadReferent() already refuses a parent that
|
|
433
|
+
* isn't there.
|
|
613
434
|
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
* access-control edge, so a move changes which queries and feeds
|
|
617
|
-
* enumerate a record and nothing about who may read it. See
|
|
618
|
-
* docs/spec/data-model.md § Reparenting.
|
|
435
|
+
* restoreVersion() deliberately does not call this — see its own comment.
|
|
436
|
+
* See docs/spec/data-model.md § Reparenting.
|
|
619
437
|
*/
|
|
620
|
-
|
|
438
|
+
private assertParentExists;
|
|
621
439
|
/**
|
|
622
440
|
* Refuse an edge that would make a record its own ancestor — asked at
|
|
623
|
-
* every site that adds one:
|
|
624
|
-
* back, and a create naming both its own id and a parent. Nothing
|
|
441
|
+
* every site that adds one: a change set naming `parentId`, a restore
|
|
442
|
+
* putting a container back, and a create naming both its own id and a parent. Nothing
|
|
625
443
|
* downstream (a generator deriving a page path, a folder view) is
|
|
626
444
|
* written to survive a cycle.
|
|
627
445
|
*
|
|
@@ -639,18 +457,6 @@ export declare class Stack implements StackClient {
|
|
|
639
457
|
* the same posture applied here.
|
|
640
458
|
* See docs/spec/data-model.md § Reparenting.
|
|
641
459
|
*/
|
|
642
|
-
/**
|
|
643
|
-
* The checks a *caller-named* destination owes, before the cycle walk:
|
|
644
|
-
* `parentId` is a real, well-formed record id. Format first, so a
|
|
645
|
-
* malformed one is a 400 naming the problem rather than a read that
|
|
646
|
-
* cannot match. Existence closes the gap between the owner path and a
|
|
647
|
-
* non-owner's, where canReadReferent() already refuses a parent that
|
|
648
|
-
* isn't there.
|
|
649
|
-
*
|
|
650
|
-
* restoreVersion() deliberately does not call this — see its own comment.
|
|
651
|
-
* See docs/spec/data-model.md § Reparenting.
|
|
652
|
-
*/
|
|
653
|
-
private assertParentExists;
|
|
654
460
|
private assertNoParentCycle;
|
|
655
461
|
/**
|
|
656
462
|
* Soft-delete a record (default) or hard-delete it permanently, removing
|
|
@@ -695,12 +501,12 @@ export declare class Stack implements StackClient {
|
|
|
695
501
|
/**
|
|
696
502
|
* Commit a per-record migration: replace `content` and `typeId` together
|
|
697
503
|
* in one step, validated against `toTypeId`'s schema exactly as
|
|
698
|
-
* create()/
|
|
504
|
+
* create()/mutate() validate against a type's schema. The single-record
|
|
699
505
|
* counterpart to migrateAll() — content here is supplied by the caller
|
|
700
506
|
* (computed client-side by the type's owning app, per
|
|
701
507
|
* docs/spec/wire-format.md § Migration commit) rather than a registered
|
|
702
508
|
* Migration function. Snapshots the prior state to version history, same
|
|
703
|
-
* as
|
|
509
|
+
* as mutate()/restoreVersion(), and takes the same optional `ifVersion`
|
|
704
510
|
* precondition every version-bumping mutation takes — checked atomically
|
|
705
511
|
* at the adapter, not here (see docs/spec/versioning.md § Optimistic
|
|
706
512
|
* concurrency).
|
|
@@ -710,7 +516,7 @@ export declare class Stack implements StackClient {
|
|
|
710
516
|
* record as it stands, so it owes both sets of integrity checks — the
|
|
711
517
|
* binding rules, the attachment rules, and `_config`'s. Missing either
|
|
712
518
|
* half would make migrate a second, unguarded write path to the same
|
|
713
|
-
* state create()/
|
|
519
|
+
* state create()/mutate() refuse to reach.
|
|
714
520
|
*/
|
|
715
521
|
commitMigration(id: string, toTypeId: TypeId, content: Record<string, unknown>, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
|
|
716
522
|
/**
|
|
@@ -734,7 +540,7 @@ export declare class Stack implements StackClient {
|
|
|
734
540
|
/**
|
|
735
541
|
* Immutability for every binding field a patch touches, then uniqueness
|
|
736
542
|
* for the subset that carries it. Fields absent from the patch carry no
|
|
737
|
-
* new claim —
|
|
543
|
+
* new claim — a content patch is a merge, so an untouched binding is the one the
|
|
738
544
|
* card already holds.
|
|
739
545
|
*/
|
|
740
546
|
private checkBindingsOnUpdate;
|
|
@@ -797,27 +603,21 @@ export declare class Stack implements StackClient {
|
|
|
797
603
|
*/
|
|
798
604
|
private checkAttachmentMimeTypeOnCreate;
|
|
799
605
|
/**
|
|
800
|
-
* filename is the only mutable field on an _attachment@1 record; fileId,
|
|
801
|
-
* size
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
|
|
805
|
-
private checkAttachmentImmutableFields;
|
|
806
|
-
/**
|
|
807
|
-
* The same immutability checkAttachmentImmutableFields() enforces, asked
|
|
808
|
-
* value-wise instead of presence-wise: a migration replaces content
|
|
809
|
-
* wholesale, so it necessarily re-sends `mimeType`, `fileId` and `size`
|
|
810
|
-
* (all required) and a presence check would refuse every migration. Only
|
|
811
|
-
* an actual change is a violation.
|
|
606
|
+
* filename is the only mutable field on an `_attachment@1` record; fileId,
|
|
607
|
+
* size and mimeType are immutable, and the correction flow is delete +
|
|
608
|
+
* re-upload. `violates` decides what counts as touching one, because the
|
|
609
|
+
* two write shapes disagree: a patch names only what it changes, while a
|
|
610
|
+
* migration replaces content wholesale and necessarily re-sends all three.
|
|
812
611
|
*
|
|
813
612
|
* Repointing `fileId` is the one that matters most: an `_attachment@1`
|
|
814
613
|
* record naming a fileId is what canAccessFile()'s uploader clause reads,
|
|
815
614
|
* so moving an existing record onto another file's hash is a route to
|
|
816
615
|
* bytes the record's author never uploaded.
|
|
616
|
+
* See docs/spec/attachments.md § The `_attachment` record type.
|
|
817
617
|
*/
|
|
818
|
-
private
|
|
618
|
+
private assertAttachmentImmutable;
|
|
819
619
|
/**
|
|
820
|
-
* `_config.entityId` defines stack ownership; neither
|
|
620
|
+
* `_config.entityId` defines stack ownership; neither patchContent() nor
|
|
821
621
|
* restoreVersion() may change it. A conflict with stack integrity, not a
|
|
822
622
|
* schema violation — hence StackConflictError. See docs/spec.md § The
|
|
823
623
|
* `_config` record.
|
|
@@ -876,12 +676,6 @@ export declare class Stack implements StackClient {
|
|
|
876
676
|
* not a failure. See docs/spec/attachments.md § Garbage collection.
|
|
877
677
|
*/
|
|
878
678
|
collectAttachmentGarbage(opts?: CollectAttachmentGarbageOptions & ActorOptions): Promise<CollectAttachmentGarbageResult>;
|
|
879
|
-
/**
|
|
880
|
-
* Flush pending writes to the underlying storage. A no-op for adapters
|
|
881
|
-
* that commit on every call (SQLite, the API adapter); meaningful for
|
|
882
|
-
* ones that buffer, and for checkpointing a stack that stays open —
|
|
883
|
-
* close() covers the teardown case on its own.
|
|
884
|
-
*/
|
|
885
679
|
/**
|
|
886
680
|
* Observe every change made through this Stack. Unscoped, so no
|
|
887
681
|
* permission filter applies — a caller holding a `Stack` already reaches
|
|
@@ -910,6 +704,12 @@ export declare class Stack implements StackClient {
|
|
|
910
704
|
* ScopedStack can refuse a scope it cannot honor; not an app-facing API.
|
|
911
705
|
*/
|
|
912
706
|
get relaysChanges(): boolean;
|
|
707
|
+
/**
|
|
708
|
+
* Flush pending writes to the underlying storage. A no-op for adapters
|
|
709
|
+
* that commit on every call (SQLite, the API adapter); meaningful for
|
|
710
|
+
* ones that buffer, and for checkpointing a stack that stays open —
|
|
711
|
+
* close() covers the teardown case on its own.
|
|
712
|
+
*/
|
|
913
713
|
flush(): Promise<void>;
|
|
914
714
|
/**
|
|
915
715
|
* Flush, then release any resources the adapter holds (connections, file
|
|
@@ -979,6 +779,15 @@ export declare class Stack implements StackClient {
|
|
|
979
779
|
* snapshotting work when the mismatch is already visible.
|
|
980
780
|
*/
|
|
981
781
|
private checkIfVersion;
|
|
782
|
+
/**
|
|
783
|
+
* The options every version-bumping adapter write carries: the `ifVersion`
|
|
784
|
+
* precondition, the prior-state snapshot that has to land in the same
|
|
785
|
+
* atomic write, and who to attribute the change to. Taken together so a
|
|
786
|
+
* new mutating verb cannot quietly omit one — `updatedVia` most of all,
|
|
787
|
+
* whose absence reads as an undelegated write.
|
|
788
|
+
* See docs/spec/versioning.md § Version history.
|
|
789
|
+
*/
|
|
790
|
+
private writeOptions;
|
|
982
791
|
/**
|
|
983
792
|
* Snapshot of a record's prior state, passed with the mutating adapter
|
|
984
793
|
* call so snapshot and mutation land in one atomic write. `associations`
|
|
@@ -989,405 +798,4 @@ export declare class Stack implements StackClient {
|
|
|
989
798
|
*/
|
|
990
799
|
private buildVersionSnapshot;
|
|
991
800
|
}
|
|
992
|
-
/** Structural equality per target arm — what dissociate() matches on. */
|
|
993
|
-
export declare function targetEqual(a: RelationshipTarget, b: RelationshipTarget): boolean;
|
|
994
|
-
export declare class ScopedStack implements StackClient {
|
|
995
|
-
private readonly stack;
|
|
996
|
-
private readonly principalEntityId;
|
|
997
|
-
private readonly subjectEntityId;
|
|
998
|
-
private readonly idTimestampSkewMs;
|
|
999
|
-
private readonly adapter;
|
|
1000
|
-
private readonly changes;
|
|
1001
|
-
constructor(stack: Stack, principalEntityId: EntityId | null, subjectEntityId: EntityId | null, idTimestampSkewMs: number | null, adapter: StackAdapter, changes: ChangeEmitter);
|
|
1002
|
-
get features(): StackFeatures;
|
|
1003
|
-
private resolveRecord;
|
|
1004
|
-
/** Whether a delegated app is acting for someone other than itself. */
|
|
1005
|
-
private get delegated();
|
|
1006
|
-
/**
|
|
1007
|
-
* Who this request is, for stamping onto whatever it mutates. Attribution
|
|
1008
|
-
* follows record-level authorship: the subject is the actor, and the
|
|
1009
|
-
* principal is named beside it only when the two differ.
|
|
1010
|
-
* See docs/spec/data-model.md § Authorship and attribution.
|
|
1011
|
-
*/
|
|
1012
|
-
private get actor();
|
|
1013
|
-
/**
|
|
1014
|
-
* Whether unconditional owner authority applies — the owner acting as
|
|
1015
|
-
* itself. The verbs that rest on it are irreversible or disclose the
|
|
1016
|
-
* sharing graph, so delegation never carries one to a subject, whichever
|
|
1017
|
-
* side the owner is on. Shared with the predicate a server applies to a
|
|
1018
|
-
* session, which decides the same tier one step earlier. See
|
|
1019
|
-
* docs/spec/access-control.md § Delegation: principal and subject.
|
|
1020
|
-
*/
|
|
1021
|
-
private get ownerActingAlone();
|
|
1022
|
-
private checkRead;
|
|
1023
|
-
private checkWrite;
|
|
1024
|
-
/**
|
|
1025
|
-
* Whether `grantee` holds a _grant covering one of `actions` for the
|
|
1026
|
-
* type's family (grants match by baseId, so a version bump never orphans
|
|
1027
|
-
* one). -own actions additionally require record.entityId === grantee,
|
|
1028
|
-
* unless `matchOwn` is false — on the principal side of a delegated
|
|
1029
|
-
* request the suffix is read as the bare verb, since which records are
|
|
1030
|
-
* reachable is the subject's business. `allowDefault` decides whether a
|
|
1031
|
-
* grant naming nobody counts. Anonymous grantees always return false.
|
|
1032
|
-
*
|
|
1033
|
-
* Reached only through subjectAllows()/principalAllows(), which fix those
|
|
1034
|
-
* two flags per side of the intersection. Call one of those instead.
|
|
1035
|
-
* See docs/spec/access-control.md § Type-level grants.
|
|
1036
|
-
*/
|
|
1037
|
-
private hasGrant;
|
|
1038
|
-
/**
|
|
1039
|
-
* The principal half of a delegated request's authority: does the app
|
|
1040
|
-
* hold any grant permitting these verbs on this type at all. Bounds what
|
|
1041
|
-
* the subject's own authority can reach through it, so a powerful app
|
|
1042
|
-
* can never lend its reach to a weaker subject — nor the reverse.
|
|
1043
|
-
* Vacuously true when there's no delegation, where the principal and
|
|
1044
|
-
* subject checks would be the same question asked twice.
|
|
1045
|
-
*
|
|
1046
|
-
* Default grants don't count here. "Any authenticated entity" is about
|
|
1047
|
-
* people who turn up, not software the owner installed — an app reaches
|
|
1048
|
-
* only the types named to it, which is the whole of what containment
|
|
1049
|
-
* promises.
|
|
1050
|
-
*
|
|
1051
|
-
* Group-targeted grants don't count here either, one step removed: a
|
|
1052
|
-
* roster is editable by any of the group's admins, so authority reaching
|
|
1053
|
-
* a principal through one would let someone other than the owner name an
|
|
1054
|
-
* app to a type. See docs/spec/access-control.md § Type-level grants.
|
|
1055
|
-
*/
|
|
1056
|
-
private principalAllows;
|
|
1057
|
-
/**
|
|
1058
|
-
* The subject half: which records are reachable, answered with `-own`
|
|
1059
|
-
* matching and default grants both in force — the ordinary reading of a
|
|
1060
|
-
* grant, since the subject is the entity a grant is written about.
|
|
1061
|
-
*
|
|
1062
|
-
* Paired with principalAllows() so that the two halves of the
|
|
1063
|
-
* intersection are the only callers of hasGrant(): its flags differ per
|
|
1064
|
-
* side and mean nothing on their own, so no call site sets them by hand.
|
|
1065
|
-
*/
|
|
1066
|
-
private subjectAllows;
|
|
1067
|
-
/**
|
|
1068
|
-
* How to refuse a record this request addressed by ID. A requester who
|
|
1069
|
-
* can read the record is told it exists and the verb was refused;
|
|
1070
|
-
* everyone else is told what a missing ID is told, so no one learns an ID
|
|
1071
|
-
* is live who could not have learned it by reading. `message` therefore
|
|
1072
|
-
* only ever reaches someone holding the record already.
|
|
1073
|
-
* See docs/spec/access-control.md § Errors and information exposure.
|
|
1074
|
-
*/
|
|
1075
|
-
private denialFor;
|
|
1076
|
-
private canRead;
|
|
1077
|
-
private checkCreateGrant;
|
|
1078
|
-
/**
|
|
1079
|
-
* `_group` records are managed, not merely written: only the owner or an
|
|
1080
|
-
* `admin` roster holder may mutate them — ordinary write permissions and
|
|
1081
|
-
* grants don't apply. Asked of both identities under delegation, like
|
|
1082
|
-
* setPermissions(). See docs/spec/identity.md § Group.
|
|
1083
|
-
*/
|
|
1084
|
-
private isGroupManager;
|
|
1085
|
-
/** Whether one identity, on its own, manages `record` — see isGroupManager(). */
|
|
1086
|
-
private managesGroup;
|
|
1087
|
-
/**
|
|
1088
|
-
* Fetch a record the subject can reach and the principal holds `update` on
|
|
1089
|
-
* (via permissions or an update grant), or throw. `mutating: false` marks
|
|
1090
|
-
* the history readers, which borrow this gate without changing anything —
|
|
1091
|
-
* the one way through it a `_grant` Record stays open to.
|
|
1092
|
-
*/
|
|
1093
|
-
private requireUpdatable;
|
|
1094
|
-
/**
|
|
1095
|
-
* Refuse a mutation aimed at a soft-deleted Record. Asked only of a
|
|
1096
|
-
* mutating caller, so the history readers borrowing this gate still work,
|
|
1097
|
-
* and only after the authority decision, so "exists but deleted" is never
|
|
1098
|
-
* a probe a stranger can run. See docs/spec/versioning.md § Mutations are
|
|
1099
|
-
* refused, not applied to a tombstone.
|
|
1100
|
-
*/
|
|
1101
|
-
private refuseIfDeleted;
|
|
1102
|
-
/**
|
|
1103
|
-
* Fetch a record the subject can reach and the principal holds `delete` on
|
|
1104
|
-
* (via permissions or a delete grant), or throw.
|
|
1105
|
-
*/
|
|
1106
|
-
private requireDeletable;
|
|
1107
|
-
/**
|
|
1108
|
-
* Whether this request may reference `recordId` (as a parentId or
|
|
1109
|
-
* relationship target). Missing and unreadable both return false —
|
|
1110
|
-
* indistinguishable, so this can't probe for a record's existence.
|
|
1111
|
-
*/
|
|
1112
|
-
private canReadReferent;
|
|
1113
|
-
/**
|
|
1114
|
-
* Whether this request can read some record referencing `fileId` —
|
|
1115
|
-
* shared by canAccessFile() and the non-owner _attachment@1 create()
|
|
1116
|
-
* carve-out, which deliberately excludes the uploader clause. Walks
|
|
1117
|
-
* every referencing record, short-circuiting on the first readable one.
|
|
1118
|
-
* `_attachment@1` records are excluded from matching outright: the
|
|
1119
|
-
* carve-out must be satisfied by some *other* record referencing the
|
|
1120
|
-
* file, never by the requester's own prior metadata record for it —
|
|
1121
|
-
* allowing that would let one successful guess unlock unlimited further
|
|
1122
|
-
* metadata records for the same fileId, reintroducing the circularity
|
|
1123
|
-
* the carve-out's uploader-clause exclusion closes.
|
|
1124
|
-
* See docs/spec/attachments.md § Creating `_attachment@1` records directly.
|
|
1125
|
-
*/
|
|
1126
|
-
private hasReadableReference;
|
|
1127
|
-
/**
|
|
1128
|
-
* Whether this request may reference or download `fileId` — the dual of
|
|
1129
|
-
* getAttachment()'s access rule. Nonexistent and inaccessible are
|
|
1130
|
-
* indistinguishable (both false), so no confirmation oracle for guessed
|
|
1131
|
-
* hashes. See docs/spec/access-control.md § Reference-creation gating.
|
|
1132
|
-
*/
|
|
1133
|
-
private canAccessFile;
|
|
1134
|
-
/** Names of the type's top-level file-ref fields — the content-reference half of attachmentFileId matching. */
|
|
1135
|
-
private fileRefFieldNames;
|
|
1136
|
-
/**
|
|
1137
|
-
* Gates file-ref content fields on file access, mirroring the
|
|
1138
|
-
* attachment-association gate — a file-ref field conveys attachment
|
|
1139
|
-
* access exactly like an `attachment` association. Only fields present
|
|
1140
|
-
* in `content` are checked (update() is a merge patch; untouched fields
|
|
1141
|
-
* carry no new reference).
|
|
1142
|
-
*/
|
|
1143
|
-
private requireFileRefAccess;
|
|
1144
|
-
/**
|
|
1145
|
-
* Reference-creation gate for one association: `attachment` requires
|
|
1146
|
-
* file access, and a `relationship` naming a record in this stack
|
|
1147
|
-
* requires read access to it. `tag` is unchecked, and `_group` roster
|
|
1148
|
-
* associations are gated by the stricter isGroupManager() instead.
|
|
1149
|
-
*
|
|
1150
|
-
* The other target arms are ungated because the gate's purpose —
|
|
1151
|
-
* refusing a reference that would convey access to, or confirm the
|
|
1152
|
-
* existence of, an unreadable record — has nothing to bite on: core
|
|
1153
|
-
* never resolves them, so no access flows through one.
|
|
1154
|
-
* See docs/spec/access-control.md § Reference-creation gating.
|
|
1155
|
-
*/
|
|
1156
|
-
private requireAssociationAccess;
|
|
1157
|
-
/**
|
|
1158
|
-
* Create a record on behalf of the subject: create grant required,
|
|
1159
|
-
* anonymous denied, entityId set to the subject, client IDs skew-checked,
|
|
1160
|
-
* reference-creating options gated, and non-owner `_attachment@1`
|
|
1161
|
-
* creation refused save one carve-out. A scoped create always stamps
|
|
1162
|
-
* authorship — an absent entityId means an unscoped `Stack` wrote it.
|
|
1163
|
-
* `createdAt`/`updatedAt` are refused to everyone but the owner acting
|
|
1164
|
-
* alone — see the guard below and docs/spec/data-model.md § Record IDs.
|
|
1165
|
-
* See also docs/spec/access-control.md and docs/spec/attachments.md.
|
|
1166
|
-
*/
|
|
1167
|
-
create<T extends Record<string, unknown> = Record<string, unknown>>(typeId: TypeId, content: T, opts?: BackdatableCreateRecordOptions): Promise<StackRecord & {
|
|
1168
|
-
content: T;
|
|
1169
|
-
}>;
|
|
1170
|
-
/**
|
|
1171
|
-
* Whether this request may decide who else reaches a record — the rule
|
|
1172
|
-
* setPermissions() enforces, asked at create time too so the reach it
|
|
1173
|
-
* withholds can't be taken one step earlier while authoring. A delegated
|
|
1174
|
-
* app is denied it: widening access is the one thing containment most
|
|
1175
|
-
* needs to hold. Refused rather than silently ignored, so an app never
|
|
1176
|
-
* believes it published something it didn't. Not `ownerActingAlone`:
|
|
1177
|
-
* the record is the subject's own, so an owner principal grants it no
|
|
1178
|
-
* reach the subject lacks.
|
|
1179
|
-
* See docs/spec/access-control.md § Delegation: principal and subject.
|
|
1180
|
-
*/
|
|
1181
|
-
private mayGrantAccess;
|
|
1182
|
-
/**
|
|
1183
|
-
* Whether one identity, on its own, may decide who else reaches `record`
|
|
1184
|
-
* — the owner-or-creator rule setPermissions() enforces, asked of one
|
|
1185
|
-
* side at a time. See
|
|
1186
|
-
* docs/spec/access-control.md § Delegation: principal and subject.
|
|
1187
|
-
*/
|
|
1188
|
-
private mayReshare;
|
|
1189
|
-
/**
|
|
1190
|
-
* A record this request may read, or null. Never throws
|
|
1191
|
-
* StackPermissionError: an unreadable record answers exactly as a missing
|
|
1192
|
-
* one does, so a caller learns only what it may read.
|
|
1193
|
-
* See docs/spec/access-control.md § Errors and information exposure.
|
|
1194
|
-
*/
|
|
1195
|
-
get(id: string, opts?: GetRecordOptions): Promise<StackRecord | null>;
|
|
1196
|
-
/**
|
|
1197
|
-
* Runs through this.query(), so canRead() still applies — this answers a
|
|
1198
|
-
* question the caller asks on its own behalf. includeUnlisted is passed
|
|
1199
|
-
* only when the request is the owner acting alone, per
|
|
1200
|
-
* docs/spec/identity.md § DID bindings.
|
|
1201
|
-
*/
|
|
1202
|
-
getEntityByDid(did: EntityId): Promise<StackRecord | null>;
|
|
1203
|
-
getOwnerEntity(): Promise<StackRecord | null>;
|
|
1204
|
-
/**
|
|
1205
|
-
* Query records, filtered to those this request can read. Pages are
|
|
1206
|
-
* filtered then refilled, so a page may slightly overshoot `limit` but
|
|
1207
|
-
* never skips a record. Grants are prefetched once, cursor-walked to
|
|
1208
|
-
* exhaustion.
|
|
1209
|
-
*/
|
|
1210
|
-
query(query?: StackQuery): Promise<QueryResult>;
|
|
1211
|
-
update(id: string, content: Record<string, unknown | null>, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1212
|
-
/**
|
|
1213
|
-
* Naming the software behind a key is the trust decision the `_app`
|
|
1214
|
-
* registry exists to record, so both halves of that binding — `did` and
|
|
1215
|
-
* `appId` — belong to the owner alone. Same reasoning that makes `_app`
|
|
1216
|
-
* ungrantable, applied to the fields a lookup reads. Registering a card
|
|
1217
|
-
* is already owner-only; without this, record-level `write` shared on a
|
|
1218
|
-
* card would be a second way in: a card carrying no DID yet could be
|
|
1219
|
-
* pointed at a write-holder's own key while keeping the name the owner
|
|
1220
|
-
* gave it, or relabelled to claim another app's `appId`. `name` and
|
|
1221
|
-
* `version` stay writable — they are display, not lookup.
|
|
1222
|
-
*
|
|
1223
|
-
* Owner *acting alone*, in both directions: a delegated app never holds
|
|
1224
|
-
* it, and an owner principal doesn't lend it to a subject holding
|
|
1225
|
-
* record-level `write` on a card — which would reopen the same route
|
|
1226
|
-
* from the other side.
|
|
1227
|
-
*
|
|
1228
|
-
* `_entity` deliberately does not get this rule: naming people is what a
|
|
1229
|
-
* contacts app does, so its cards stay writable by grant. Uniqueness and
|
|
1230
|
-
* immutability still bind them. See docs/spec/identity.md § DID bindings.
|
|
1231
|
-
*/
|
|
1232
|
-
private requireOwnerForAppIdentity;
|
|
1233
|
-
/**
|
|
1234
|
-
* A self-reported `appId` must agree with the `_app` card naming the
|
|
1235
|
-
* principal's DID, where the owner registered one. `principalId` is
|
|
1236
|
-
* verified, so letting the pair disagree would leave a verified principal
|
|
1237
|
-
* claiming a name the owner gave different software — the cross-check the
|
|
1238
|
-
* registry exists for, refused at the write instead of left to each reader.
|
|
1239
|
-
* A principal with no card keeps `appId` as the bare self-report it is for
|
|
1240
|
-
* every undelegated writer.
|
|
1241
|
-
* See docs/spec/identity.md § Attribution and what can be trusted.
|
|
1242
|
-
*/
|
|
1243
|
-
private requireAppIdMatchesPrincipal;
|
|
1244
|
-
/**
|
|
1245
|
-
* The owner's own DID is the one `_entity` binding a grantee may not claim.
|
|
1246
|
-
* `ownerProfile` adopts whichever card holds it, so a card minted by
|
|
1247
|
-
* someone else becomes the stack's own profile, and uniqueness then makes
|
|
1248
|
-
* that permanent. Every other DID stays open to a contacts app, which is
|
|
1249
|
-
* the reach `_entity` is grantable for.
|
|
1250
|
-
* See docs/spec/identity.md § DID bindings.
|
|
1251
|
-
*/
|
|
1252
|
-
private requireOwnerForOwnerDid;
|
|
1253
|
-
/**
|
|
1254
|
-
* A `_grant` Record *is* authority, so rewriting one is the escalation
|
|
1255
|
-
* UNGRANTABLE_SYSTEM_TYPES refuses at evaluation, reached by editing an
|
|
1256
|
-
* existing grant rather than minting a fresh one. `grant()` and `revoke()`
|
|
1257
|
-
* live on `Stack`, never `StackClient`, so no scoped write is lost. Writes
|
|
1258
|
-
* only: reading a grant Record and its history stays on the ordinary gate.
|
|
1259
|
-
* See docs/spec/access-control.md § Type-level grants.
|
|
1260
|
-
*/
|
|
1261
|
-
private requireOwnerForGrantRecord;
|
|
1262
|
-
associate(id: string, association: Association, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1263
|
-
dissociate(id: string, association: Association, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1264
|
-
setPermissions(id: string, permissions: Permission[], opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1265
|
-
/**
|
|
1266
|
-
* Withhold a record from enumeration, or restore it — gated exactly like
|
|
1267
|
-
* setPermissions(), since both decide who or what can discover the
|
|
1268
|
-
* record rather than merely read it once found. See
|
|
1269
|
-
* docs/spec/unlisted.md.
|
|
1270
|
-
*/
|
|
1271
|
-
setUnlisted(id: string, unlisted: boolean, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1272
|
-
/**
|
|
1273
|
-
* Move a record into another container, or to the root with `null`.
|
|
1274
|
-
* Gated as an ordinary write on the record, plus read access to the
|
|
1275
|
-
* destination — the same reference gate `create()` applies to a
|
|
1276
|
-
* `parentId`, asked again here so a move cannot reach a container an
|
|
1277
|
-
* authoring call could not have named. Not reshare-gated like
|
|
1278
|
-
* setUnlisted(): containment decides which listings enumerate a record,
|
|
1279
|
-
* never who may read it, so a move discloses it to nobody who could not
|
|
1280
|
-
* already read it. The origin is ungated — knowing it requires reading
|
|
1281
|
-
* the record, which this caller has already had to do. See
|
|
1282
|
-
* docs/spec/access-control.md § Reference-creation gating.
|
|
1283
|
-
*/
|
|
1284
|
-
setParent(id: string, parentId: string | null, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1285
|
-
/**
|
|
1286
|
-
* Hard delete is owner-only: it is irreversible and destroys version
|
|
1287
|
-
* history, so neither the write bit nor delete-own/delete-any grants
|
|
1288
|
-
* reach it, and delegation doesn't carry it either. Everyone else is
|
|
1289
|
-
* limited to soft delete.
|
|
1290
|
-
*/
|
|
1291
|
-
delete(id: string, opts?: DeleteRecordOptions): Promise<void>;
|
|
1292
|
-
/**
|
|
1293
|
-
* Reverse a soft delete. Gated the same as delete() — undelete is the
|
|
1294
|
-
* inverse of soft delete, so granting one direction without the other
|
|
1295
|
-
* would be backwards. Idempotent, per Stack.undelete().
|
|
1296
|
-
*/
|
|
1297
|
-
undelete(id: string, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1298
|
-
/**
|
|
1299
|
-
* History is the mutation/recovery surface, not a read surface — gated
|
|
1300
|
-
* like update(), with snapshot `permissions` stripped for everyone but
|
|
1301
|
-
* the owner acting alone — a snapshot's permissions are the stack's
|
|
1302
|
-
* sharing graph, which delegation is not a route to. Reading history
|
|
1303
|
-
* changes nothing, so it is the one path the `_grant` write fence leaves
|
|
1304
|
-
* alone: seeing how a Record you can already read got that way is not
|
|
1305
|
-
* the escalation that fence exists to stop, and losing it would leave a
|
|
1306
|
-
* write-holder unable to audit the Record they hold.
|
|
1307
|
-
* See docs/spec/versioning.md § History access.
|
|
1308
|
-
*/
|
|
1309
|
-
getVersions(id: string): Promise<RecordVersion[]>;
|
|
1310
|
-
/** See getVersions() — same mutate-surface gate, same permissions stripping. */
|
|
1311
|
-
getVersion(id: string, version: number): Promise<RecordVersion | null>;
|
|
1312
|
-
/**
|
|
1313
|
-
* Re-runs the reference-creation checks against the snapshot, so a
|
|
1314
|
-
* restore can't re-convey access to a file or record the subject can no
|
|
1315
|
-
* longer reach today — the snapshot's `parentId` among them, gated
|
|
1316
|
-
* exactly as setParent() gates a destination named directly. Only the
|
|
1317
|
-
* owner acting alone is exempt: under delegation the checks resolve
|
|
1318
|
-
* against the subject, which is whose reach the restore would widen.
|
|
1319
|
-
* See docs/spec/versioning.md § Restore semantics.
|
|
1320
|
-
*/
|
|
1321
|
-
restoreVersion(id: string, version: number, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1322
|
-
/**
|
|
1323
|
-
* Commit a per-record migration — **the owner acting alone, only**.
|
|
1324
|
-
*
|
|
1325
|
-
* Migration is owner-driven by design: `migrateAll()`, the bulk path,
|
|
1326
|
-
* lives on `Stack` and is deliberately absent from `StackClient`, the
|
|
1327
|
-
* same way `grant()`/`revoke()` are. This is its per-record counterpart
|
|
1328
|
-
* and carries the same restriction, rather than inventing a grant model
|
|
1329
|
-
* that the bulk path deliberately doesn't have.
|
|
1330
|
-
*
|
|
1331
|
-
* The restriction is what makes the verb safe to expose at all. Migrate
|
|
1332
|
-
* replaces `content` and `typeId` wholesale, so a grant-based version
|
|
1333
|
-
* would have to re-derive every gate `create()` applies at the
|
|
1334
|
-
* destination *and* every gate `update()` applies over the existing
|
|
1335
|
-
* content, and would reopen each one it missed. The sharpest is the
|
|
1336
|
-
* non-owner `_attachment@1` refusal create() carries: without it, a
|
|
1337
|
-
* requester holding a create grant on `_attachment@1` and write access
|
|
1338
|
-
* to any record they authored could migrate that record into the family
|
|
1339
|
-
* naming any `fileId`, then read the bytes through canAccessFile()'s
|
|
1340
|
-
* uploader clause — the exact escalation that carve-out exists to refuse
|
|
1341
|
-
* (see docs/spec/attachments.md § Creating `_attachment@1` records
|
|
1342
|
-
* directly). Ordinary write access to a record is not consent to move it
|
|
1343
|
-
* between families.
|
|
1344
|
-
*
|
|
1345
|
-
* A server implementing `POST /records/:id/migrate` therefore serves it
|
|
1346
|
-
* to the stack owner and answers 403 otherwise. See
|
|
1347
|
-
* docs/spec/data-model.md § Type migrations.
|
|
1348
|
-
*/
|
|
1349
|
-
commitMigration(id: string, toTypeId: TypeId, content: Record<string, unknown>, opts?: IfVersionOptions): Promise<StackRecord>;
|
|
1350
|
-
/**
|
|
1351
|
-
* Store bytes and create an _attachment@1 metadata record (create grant
|
|
1352
|
-
* on `_attachment@1` required; anonymous denied), returning that record.
|
|
1353
|
-
* Authorship and principal are stamped exactly as create() does.
|
|
1354
|
-
*/
|
|
1355
|
-
putAttachment(data: Uint8Array, mimeType: string, filename?: string, appId?: AppId): Promise<StackRecord & {
|
|
1356
|
-
content: AttachmentContent;
|
|
1357
|
-
}>;
|
|
1358
|
-
/**
|
|
1359
|
-
* Download attachment bytes. Accessible to the owner, a reader of any
|
|
1360
|
-
* referencing record, or the uploader pre-association — the same
|
|
1361
|
-
* predicate as the reference-creation gate (canAccessFile).
|
|
1362
|
-
*/
|
|
1363
|
-
getAttachment(fileId: string): Promise<Uint8Array>;
|
|
1364
|
-
/**
|
|
1365
|
-
* Delete an attachment. Only the stack owner may delete attachments.
|
|
1366
|
-
* Delegates to Stack.deleteAttachment(), which enforces the "not referenced" check.
|
|
1367
|
-
*/
|
|
1368
|
-
deleteAttachment(fileId: string): Promise<void>;
|
|
1369
|
-
/**
|
|
1370
|
-
* Sweep for unreferenced attachment bytes and delete them. Only the stack
|
|
1371
|
-
* owner may run this. Delegates to Stack.collectAttachmentGarbage().
|
|
1372
|
-
*/
|
|
1373
|
-
collectAttachmentGarbage(opts?: CollectAttachmentGarbageOptions): Promise<CollectAttachmentGarbageResult>;
|
|
1374
|
-
/**
|
|
1375
|
-
* Observe the changes this request may read. The predicate is canRead
|
|
1376
|
-
* applied per event — the same one get() and query() answer with, so a
|
|
1377
|
-
* feed can't disagree with them about what this session sees.
|
|
1378
|
-
*
|
|
1379
|
-
* A record the subscriber cannot read produces no event at all, rather
|
|
1380
|
-
* than an empty or redacted one: the existence of a change is itself a
|
|
1381
|
-
* disclosure, the same reasoning that keeps a count of the whole match
|
|
1382
|
-
* off a query result. See docs/spec/events.md § Permission scoping.
|
|
1383
|
-
*/
|
|
1384
|
-
subscribe(handler: (change: RecordChange) => void, opts?: SubscribeOptions): Promise<Unsubscribe>;
|
|
1385
|
-
/**
|
|
1386
|
-
* canRead for one event, through the subscription's own cache. Grants
|
|
1387
|
-
* are prefetched once and refilled after an invalidation rather than
|
|
1388
|
-
* re-queried per event, which would be a `_grant` scan for every change
|
|
1389
|
-
* the stack makes.
|
|
1390
|
-
*/
|
|
1391
|
-
private canReadCached;
|
|
1392
|
-
}
|
|
1393
801
|
//# sourceMappingURL=stack.d.ts.map
|