js-bao 0.5.0 → 0.5.2

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.
@@ -31,8 +31,32 @@ interface FieldOptions {
31
31
  unique?: boolean;
32
32
  default?: any | (() => any);
33
33
  autoAssign?: boolean;
34
+ /**
35
+ * Auto-timestamp this field with `Date.now()` (milliseconds) on save.
36
+ *
37
+ * - `'create'` — stamp only on the first save (when `isNew === true`).
38
+ * - `'update'` — stamp on every save (create AND update).
39
+ * - `'both'` — stamp on every save (create AND update).
40
+ *
41
+ * If the caller passes an explicit value for the field in `data`, the
42
+ * explicit value wins and the stamp is skipped. The stamp is applied
43
+ * in `beforeSave` BEFORE any user-defined hooks, so user hooks may
44
+ * still overwrite the value if they wish (last writer wins).
45
+ */
46
+ autoStamp?: "create" | "update" | "both";
34
47
  maxLength?: number;
35
48
  maxCount?: number;
49
+ /**
50
+ * Allowed-value set for a `string` field. When present, the codegen
51
+ * generators (database-type codegen + doc-model v2 codegen) emit a
52
+ * TypeScript string-literal union (`"a" | "b" | "c"`) instead of a bare
53
+ * `string` for this field.
54
+ *
55
+ * Advisory / codegen-only: this is a TS-emission hint. The runtime and the
56
+ * server do NOT enforce enum membership on write (see #843). Only valid on
57
+ * `string` fields, and must be a non-empty array of strings.
58
+ */
59
+ enum?: string[];
36
60
  }
37
61
  interface UniqueConstraintConfig {
38
62
  name: string;
@@ -957,6 +981,18 @@ interface HookResult {
957
981
  allow: boolean;
958
982
  /** Reason for denial (returned to client as error message) */
959
983
  reason?: string;
984
+ /**
985
+ * Names of fields the hook wrote onto `ctx.data` that the DO should echo
986
+ * back to the caller in the write response's `appliedFields`. A `beforeSave`
987
+ * hook can compute and set fields on `ctx.data` (e.g. app-level triggers that
988
+ * derive a value from the submitted row); those values are persisted but are
989
+ * invisible to a caller that rebuilds a change event from its original
990
+ * submission inputs. Reporting the names here lets the DO read the final
991
+ * post-hook values back and thread them into `appliedFields` so the caller
992
+ * can propagate them (#1476). The hook contract stays generic — it names the
993
+ * fields it applied and does not need to know how the caller uses them.
994
+ */
995
+ serverAppliedFieldNames?: string[];
960
996
  }
961
997
  /** Result from beforeQuery hooks — can inject additional filters */
962
998
  interface BeforeQueryResult extends HookResult {
@@ -1023,6 +1059,25 @@ interface SaveRequest {
1023
1059
  ifNotExists?: boolean;
1024
1060
  condition?: DocumentFilter;
1025
1061
  upsertOn?: string;
1062
+ /**
1063
+ * Per-call auto-timestamp directives for schemaless writes.
1064
+ *
1065
+ * Each entry is a list of field names to stamp with `Date.now()`
1066
+ * (milliseconds) at the corresponding lifecycle event:
1067
+ *
1068
+ * - `create` — field is stamped only when the record is new
1069
+ * (no row exists yet for this `modelName` + `id`).
1070
+ * - `update` — field is stamped on every save (create AND update).
1071
+ *
1072
+ * If the caller already provided an explicit value for the field
1073
+ * in `data`, the explicit value wins and the stamp is skipped.
1074
+ * Stamping happens BEFORE the `beforeSave` user hook runs, so user
1075
+ * hooks may still overwrite the stamped value if they wish.
1076
+ */
1077
+ autoStamps?: {
1078
+ create?: string[];
1079
+ update?: string[];
1080
+ };
1026
1081
  }
1027
1082
  interface DeleteRequest {
1028
1083
  modelName: string;
@@ -1064,6 +1119,20 @@ interface QueryResponse {
1064
1119
  interface SaveResponse {
1065
1120
  success: boolean;
1066
1121
  id: string;
1122
+ /**
1123
+ * Server-applied field values the DO wrote AFTER the caller's submission
1124
+ * inputs were assembled, read back from the final post-hook `data`
1125
+ * (#1476, consolidating the #1462 `stamps` channel). This is the single
1126
+ * "server-applied fields" channel: it carries both the applied `autoStamps`
1127
+ * timestamps and any fields a `beforeSave` hook reported via
1128
+ * `serverAppliedFieldNames` (e.g. app-level trigger-computed values). Present
1129
+ * only when at least one such field ended up with a value. Lets the Worker
1130
+ * thread these values into the change-event `data` it broadcasts to
1131
+ * subscribers — otherwise a `select`-listed stamped or trigger-computed field
1132
+ * never reaches the frame, because the frame is rebuilt from the submission
1133
+ * inputs (which predate the DO-side write).
1134
+ */
1135
+ appliedFields?: Record<string, any>;
1067
1136
  }
1068
1137
  interface DeleteResponse {
1069
1138
  success: boolean;
@@ -1131,10 +1200,23 @@ interface PatchRequest {
1131
1200
  data: Record<string, any>;
1132
1201
  stringSets?: Record<string, string[]>;
1133
1202
  condition?: DocumentFilter;
1203
+ /**
1204
+ * Per-call auto-timestamp directives. A patch is always treated as
1205
+ * an update (`isNew === false`), so only the `update` list is
1206
+ * applied; the `create` list is ignored on patch.
1207
+ *
1208
+ * Explicit values in `data` win.
1209
+ */
1210
+ autoStamps?: {
1211
+ create?: string[];
1212
+ update?: string[];
1213
+ };
1134
1214
  }
1135
1215
  interface PatchResponse {
1136
1216
  success: boolean;
1137
1217
  id: string;
1218
+ /** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
1219
+ appliedFields?: Record<string, any>;
1138
1220
  }
1139
1221
  interface BatchOperation {
1140
1222
  op: "save" | "patch" | "delete" | "increment" | "addToSet" | "removeFromSet";
@@ -1146,15 +1228,62 @@ interface BatchOperation {
1146
1228
  ifNotExists?: boolean;
1147
1229
  condition?: DocumentFilter;
1148
1230
  upsertOn?: string;
1231
+ /**
1232
+ * Per-operation auto-timestamp directives. Same semantics as
1233
+ * `SaveRequest.autoStamps` / `PatchRequest.autoStamps`. Applied
1234
+ * BEFORE the user `beforeSave` hook runs, with explicit values
1235
+ * in `data` always winning.
1236
+ *
1237
+ * For `op: "patch"`, only the `update` list applies (patches are
1238
+ * always treated as updates).
1239
+ */
1240
+ autoStamps?: {
1241
+ create?: string[];
1242
+ update?: string[];
1243
+ };
1149
1244
  }
1150
1245
  interface BatchRequest {
1151
1246
  operations: BatchOperation[];
1247
+ /**
1248
+ * All-or-nothing execution (#1478). When `true`, the FIRST operation that
1249
+ * fails inside the transaction — hook denial, condition / `ifNotExists` /
1250
+ * unique violation, record-not-found, or upsert validation error — aborts
1251
+ * the whole batch: every write is rolled back and the handler returns an
1252
+ * error naming the failed step with its natural HTTP status (see
1253
+ * `BatchOperationResult.status`). When `false` or absent (the default), each
1254
+ * operation's outcome is recorded independently and successful siblings
1255
+ * commit — the partial-success behavior import-bulk and applyToQuery rely on.
1256
+ */
1257
+ atomic?: boolean;
1152
1258
  }
1259
+ /**
1260
+ * Stable, machine-readable failure tag on a failed `BatchOperationResult`
1261
+ * (#1478). Derived at each failure site — never inferred from the `error`
1262
+ * string. `#1389` (conditional writes + fencing-token enforcement) surfaces its
1263
+ * condition / stale-token rejections through this same channel.
1264
+ */
1265
+ type BatchFailureCode = "NOT_FOUND" | "HOOK_DENIED" | "CONDITION_NOT_MET" | "ALREADY_EXISTS" | "UNIQUE_VIOLATION" | "VALIDATION_ERROR";
1153
1266
  interface BatchOperationResult {
1154
1267
  success: boolean;
1155
1268
  id: string;
1156
1269
  error?: string;
1270
+ /**
1271
+ * Structured failure metadata (#1478), present only on a failed result. Both
1272
+ * are derived at the failure site, not parsed back out of `error`:
1273
+ * - `status` — the natural HTTP status for the cause (404 not found,
1274
+ * 403 hook denial, 409 condition / `ifNotExists` / unique conflict,
1275
+ * 400 upsert validation).
1276
+ * - `code` — a stable machine-readable tag (see `BatchFailureCode`).
1277
+ *
1278
+ * In atomic mode the handler reads the first failure's `status`/`code` to
1279
+ * build the error response, so an increment-on-missing op keeps its 404 and a
1280
+ * hook denial keeps its 403 instead of collapsing to a uniform 409.
1281
+ */
1282
+ status?: number;
1283
+ code?: BatchFailureCode;
1157
1284
  values?: Record<string, number>;
1285
+ /** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
1286
+ appliedFields?: Record<string, any>;
1158
1287
  }
1159
1288
  interface BatchResponse {
1160
1289
  results: BatchOperationResult[];
@@ -1186,6 +1315,7 @@ interface SchemaFieldInfo {
1186
1315
  required?: boolean;
1187
1316
  default?: string | number | boolean;
1188
1317
  autoAssign?: boolean;
1318
+ autoStamp?: "create" | "update" | "both";
1189
1319
  maxLength?: number;
1190
1320
  maxCount?: number;
1191
1321
  }
@@ -1207,6 +1337,35 @@ interface SchemaModelInfo {
1207
1337
  interface SchemaResponse {
1208
1338
  models: Record<string, SchemaModelInfo>;
1209
1339
  }
1340
+ /**
1341
+ * Return an error message if `fieldName` is a reserved field name
1342
+ * (matches the policy enforced by `_checkReservedFields` on incoming
1343
+ * record data). Returns `null` if the name is acceptable.
1344
+ *
1345
+ * "id" is allowed (stripped before storage in the JSON column).
1346
+ * "type" maps to the internal `_type` column. Any name starting with
1347
+ * "_" is internal.
1348
+ *
1349
+ * @internal
1350
+ */
1351
+ declare function checkReservedFieldName(fieldName: string): string | null;
1352
+ /**
1353
+ * Validate that none of the field names listed in `autoStamps.create` /
1354
+ * `autoStamps.update` are reserved. Returns an error message on the
1355
+ * first violation, or `null` if everything is acceptable.
1356
+ *
1357
+ * Codex P2 #2: `_checkReservedFields` only inspects the keys already
1358
+ * present in `data`. Without this check, a request such as
1359
+ * `{ autoStamps: { update: ["type"] } }` would pass the data-level
1360
+ * guard and then have `applyAutoStamps` add a reserved key after the
1361
+ * fact, bypassing the protection.
1362
+ *
1363
+ * @internal
1364
+ */
1365
+ declare function checkAutoStampsReservedFields(autoStamps: {
1366
+ create?: string[];
1367
+ update?: string[];
1368
+ } | undefined): string | null;
1210
1369
  /**
1211
1370
  * Create a Durable Object class for schemaless database storage.
1212
1371
  *
@@ -1356,6 +1515,33 @@ declare function createDatabaseDO(config?: DatabaseDOConfig): {
1356
1515
  */
1357
1516
  declare const createDocumentDO: typeof createDatabaseDO;
1358
1517
 
1518
+ /**
1519
+ * Decoding for rows read out of the schemaless `records` table.
1520
+ *
1521
+ * A `SELECT` built by `JsonQueryTranslator` comes back in one of two shapes:
1522
+ * the whole record (`_id`, `_type`, `_data`), or — under an inclusion
1523
+ * projection — one column per projected field. Both shapes are turned back
1524
+ * into a record object here, so every reader of the store (the DatabaseDO's
1525
+ * query/get paths, the per-document SQLite projection) hands callers the same
1526
+ * values for the same stored record.
1527
+ */
1528
+ /**
1529
+ * Column-alias prefix for a projected field whose value is selected as JSON
1530
+ * text. The prefix marks the column as still-encoded so
1531
+ * {@link parseRecordRow} knows to decode it; field names cannot start with
1532
+ * `_` (the write path rejects them as reserved), so the prefix can never
1533
+ * collide with a real field.
1534
+ */
1535
+ declare const PROJECTED_JSON_ALIAS_PREFIX = "_json_";
1536
+ /** Column alias under which a projected field's JSON text is selected. */
1537
+ declare function projectedJsonAlias(fieldName: string): string;
1538
+ /**
1539
+ * Decode one row of a `records` SELECT into a record object: `_id`/`_type`
1540
+ * become `id`/`type`, `_data` is parsed when present, and any projected
1541
+ * column is decoded back to its stored value.
1542
+ */
1543
+ declare function parseRecordRow(row: Record<string, any>): Record<string, any>;
1544
+
1359
1545
  /**
1360
1546
  * Cloudflare Worker Router Template
1361
1547
  *
@@ -1432,6 +1618,7 @@ interface DiscoveredField {
1432
1618
  required?: boolean;
1433
1619
  default?: string | number | boolean;
1434
1620
  autoAssign?: boolean;
1621
+ autoStamp?: "create" | "update" | "both";
1435
1622
  maxLength?: number;
1436
1623
  maxCount?: number;
1437
1624
  }
@@ -1676,6 +1863,17 @@ declare class BaseModel implements StringSetChangeTracker {
1676
1863
  private ensureLocalChanges;
1677
1864
  private hasLocalChange;
1678
1865
  private getFromYjs;
1866
+ /**
1867
+ * Defensive normalization for the read/sync boundary (issue #625).
1868
+ *
1869
+ * Stringset fields are owned by getStringSetFromYjs (which expects the
1870
+ * raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
1871
+ * through untouched. For any other field, a composite Yjs primitive
1872
+ * (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
1873
+ * raw: normalize it to its plain-JS projection (toJSON / toString) and
1874
+ * warn, rather than throwing. Scalars pass through unchanged.
1875
+ */
1876
+ private normalizeNonStringSetValue;
1679
1877
  private getValue;
1680
1878
  private setValue;
1681
1879
  get isDirty(): boolean;
@@ -1810,6 +2008,27 @@ declare class BaseModel implements StringSetChangeTracker {
1810
2008
  */
1811
2009
  static count(this: new (...args: any[]) => any, filter?: DocumentFilter, options?: Pick<QueryOptions, "documents">): Promise<number>;
1812
2010
  static findAll<T extends BaseModel>(this: new (...args: any[]) => T): Promise<T[]>;
2011
+ /**
2012
+ * Resolve a record by its unique-constraint field values via an engine query.
2013
+ * Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
2014
+ * entry for a key.
2015
+ *
2016
+ * The index map is a cache over the SQLite projection, not the source of
2017
+ * truth: every record is loaded into the projection on `initializeForDocument`
2018
+ * regardless of index presence, but a record written by a client predating
2019
+ * index maintenance (legacy data) is missing from the index. Such a record
2020
+ * silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
2021
+ * check. A query over the constraint fields finds it reliably.
2022
+ *
2023
+ * Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
2024
+ * the document that holds it, via `queryOne`) or `null`. Callers decide
2025
+ * whether to heal the missing index entry (write paths) and do their own
2026
+ * logging — this method never mutates the index, so read paths stay
2027
+ * read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
2028
+ * constraint field means the record does not participate in the constraint,
2029
+ * so no query runs.
2030
+ */
2031
+ private static _resolveByUniqueFallback;
1813
2032
  static findByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, value: any | any[]): Promise<T | null>;
1814
2033
  static upsertByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, uniqueLookupValue: any | any[], dataToUpsert: Partial<Omit<InstanceType<new (...args: any[]) => T>, keyof BaseModel | "toJSON">> & {
1815
2034
  id?: string;
@@ -1910,6 +2129,11 @@ declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSche
1910
2129
 
1911
2130
  /**
1912
2131
  * Infer a _meta_ type string from a JS runtime value.
2132
+ *
2133
+ * Only an all-`true` Y.Map (the stringset wire shape) is tagged as
2134
+ * `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
2135
+ * Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
2136
+ * than being mis-tagged.
1913
2137
  */
1914
2138
  declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
1915
2139
  /**
@@ -1941,4 +2165,16 @@ declare function syncModelMeta(yDoc: Y.Doc, modelName: string, schema: ModelSche
1941
2165
  */
1942
2166
  declare function syncInferredMeta(yDoc: Y.Doc, modelName: string, recordData: Record<string, any>): void;
1943
2167
 
1944
- export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation$1 as AggregationOperation, type AggregationOptions$1 as AggregationOptions, type AggregationResult$1 as AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, type CountRequest, type CountResponse, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DocumentDOConfig, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type Env, type ErrorResponse, type GroupByField$1 as GroupByField, type HookContext, type HookResult, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type PatchRequest, type PatchResponse, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SqlStorage, type SqlStorageCursor, type StringSetMembership$1 as StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, clearMetaSyncCache, createDatabaseDO, createDocumentDO, discoverModelNames, discoverSchema, handleRequest, inferFieldType, loadSchemaFromTomlString, registerFunctionDefault, schemaToToml, syncInferredMeta, syncModelMeta };
2168
+ /**
2169
+ * Produce a deterministic, SQL-safe alias suffix derived from arbitrary input.
2170
+ * The resulting string always matches our identifier policy when prefixed with
2171
+ * a valid identifier segment.
2172
+ */
2173
+ declare function deterministicIdentifierHash(value: string): string;
2174
+ /**
2175
+ * Build a safe alias using a prefix that already conforms to the identifier
2176
+ * policy along with a deterministic hash of the raw descriptor.
2177
+ */
2178
+ declare function buildSafeAlias(prefix: string, rawDescriptor: string): string;
2179
+
2180
+ export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation$1 as AggregationOperation, type AggregationOptions$1 as AggregationOptions, type AggregationResult$1 as AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, type CountRequest, type CountResponse, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DocumentDOConfig, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type Env, type ErrorResponse, type GroupByField$1 as GroupByField, type HookContext, type HookResult, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, PROJECTED_JSON_ALIAS_PREFIX, type PatchRequest, type PatchResponse, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SqlStorage, type SqlStorageCursor, type StringSetMembership$1 as StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, createDatabaseDO, createDocumentDO, deterministicIdentifierHash, discoverModelNames, discoverSchema, handleRequest, inferFieldType, loadSchemaFromTomlString, parseRecordRow, projectedJsonAlias, registerFunctionDefault, schemaToToml, syncInferredMeta, syncModelMeta };