@lunora/do 1.0.0-alpha.17 → 1.0.0-alpha.19

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/dist/index.d.mts CHANGED
@@ -2800,8 +2800,14 @@ interface StorageRulesResult {
2800
2800
  * package's tests and the studio's fails the build if the two key sets diverge.
2801
2801
  */
2802
2802
  interface StudioFeaturesResult {
2803
+ /** `@lunora/bindings/analytics` / `ctx.analytics` is used, or it is a declared dependency. */
2804
+ analytics: boolean;
2805
+ /** `@lunora/auth` is a declared dependency (backs the Users / Sessions / Organizations / Configuration pages). */
2806
+ auth: boolean;
2803
2807
  /** `@lunora/flags` / `ctx.flags` is used, or it is a declared dependency. */
2804
2808
  flags: boolean;
2809
+ /** `@lunora/bindings/kv` / `ctx.kv` is used, or it is a declared dependency. */
2810
+ kv: boolean;
2805
2811
  /** `@lunora/mail` is imported by a `lunora/` source or a declared dependency. */
2806
2812
  mail: boolean;
2807
2813
  /** `@lunora/payment` is used (import or `ctx.payments`) or a declared dependency. */
@@ -6597,8 +6603,11 @@ interface WhereSqlStrategy {
6597
6603
  fieldRef: FieldRefSql;
6598
6604
  /**
6599
6605
  * Dialect `contains` rendering given the field reference and the (already
6600
- * bound) search term. Absent ⇒ the portable `… LIKE '%' || term || '%'`
6601
- * concat form (SQLite/Postgres); MySQL supplies a `CONCAT(...)` variant.
6606
+ * bound, already wildcard-escaped) search term. Absent ⇒ the portable
6607
+ * `… LIKE '%' || term || '%' ESCAPE '\'` concat form (SQLite/Postgres); MySQL
6608
+ * supplies a `CONCAT(...)` variant. The term is escaped by
6609
+ * {@link compileContains}, so an implementation MUST pair it with
6610
+ * `ESCAPE '\'` for the literal-match to hold.
6602
6611
  */
6603
6612
  likeContains?: (reference: SQL, term: SQL) => SQL;
6604
6613
  /**
package/dist/index.d.ts CHANGED
@@ -2800,8 +2800,14 @@ interface StorageRulesResult {
2800
2800
  * package's tests and the studio's fails the build if the two key sets diverge.
2801
2801
  */
2802
2802
  interface StudioFeaturesResult {
2803
+ /** `@lunora/bindings/analytics` / `ctx.analytics` is used, or it is a declared dependency. */
2804
+ analytics: boolean;
2805
+ /** `@lunora/auth` is a declared dependency (backs the Users / Sessions / Organizations / Configuration pages). */
2806
+ auth: boolean;
2803
2807
  /** `@lunora/flags` / `ctx.flags` is used, or it is a declared dependency. */
2804
2808
  flags: boolean;
2809
+ /** `@lunora/bindings/kv` / `ctx.kv` is used, or it is a declared dependency. */
2810
+ kv: boolean;
2805
2811
  /** `@lunora/mail` is imported by a `lunora/` source or a declared dependency. */
2806
2812
  mail: boolean;
2807
2813
  /** `@lunora/payment` is used (import or `ctx.payments`) or a declared dependency. */
@@ -6597,8 +6603,11 @@ interface WhereSqlStrategy {
6597
6603
  fieldRef: FieldRefSql;
6598
6604
  /**
6599
6605
  * Dialect `contains` rendering given the field reference and the (already
6600
- * bound) search term. Absent ⇒ the portable `… LIKE '%' || term || '%'`
6601
- * concat form (SQLite/Postgres); MySQL supplies a `CONCAT(...)` variant.
6606
+ * bound, already wildcard-escaped) search term. Absent ⇒ the portable
6607
+ * `… LIKE '%' || term || '%' ESCAPE '\'` concat form (SQLite/Postgres); MySQL
6608
+ * supplies a `CONCAT(...)` variant. The term is escaped by
6609
+ * {@link compileContains}, so an implementation MUST pair it with
6610
+ * `ESCAPE '\'` for the literal-match to hold.
6602
6611
  */
6603
6612
  likeContains?: (reference: SQL, term: SQL) => SQL;
6604
6613
  /**
package/dist/index.mjs CHANGED
@@ -3,15 +3,15 @@ export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, norma
3
3
  export { aggregateTableName, coerceAggregateNumber, encodeAggregateKey, foldAggregateTally, readAggregateValue } from './packem_shared/aggregateTableName-CxNqY1Sl.mjs';
4
4
  export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy } from './packem_shared/CountRlsUnsupportedError-28ZvvwKS.mjs';
5
5
  export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, ensureAuthMetricsTables, readAuthMetrics, recordAuthEvent } from './packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs';
6
- export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-BXn2j5vu.mjs';
6
+ export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-Do5h_jiW.mjs';
7
7
  export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-DfPxn8I0.mjs';
8
8
  export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
9
9
  export { renderSql } from './packem_shared/renderSql-D6eUcn2N.mjs';
10
10
  export { diffExternalSource } from './packem_shared/diffExternalSource-Cx9HUPJj.mjs';
11
- export { materializeExternalRows, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-uKBx9H0s.mjs';
12
- export { isSourceDue, liftSourceId, pullExternalSourceTick } from './packem_shared/isSourceDue-BMTJv65e.mjs';
11
+ export { materializeExternalRows, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-Bj6EXy50.mjs';
12
+ export { isSourceDue, liftSourceId, pullExternalSourceTick } from './packem_shared/isSourceDue-9mJRJ9Ld.mjs';
13
13
  export { FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_TABLE, ensureFunctionMetricsTables, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, recordFunctionMetric } from './packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
14
- export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-fMl1vyt1.mjs';
14
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-BvGiv2u6.mjs';
15
15
  export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
16
16
  export { MAIL_RETENTION, MAIL_TABLE, clearCapturedMail, ensureMailTable, readCapturedMail, recordCapturedMail } from './packem_shared/MAIL_RETENTION-CPpgl-dX.mjs';
17
17
  export { default as NotFoundError } from './packem_shared/NotFoundError-CMuMZt81.mjs';
@@ -19,20 +19,20 @@ export { armRestore, readBookmark } from './packem_shared/armRestore-BJk53Ro8.mj
19
19
  export { applySelect, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope } from './packem_shared/applySelect-BvZdFUBT.mjs';
20
20
  export { RANK_TIEBREAK, encodePartitionKey, matchesRankStaticWhere, rankTableName, resolveRankPartition, sortColumnName } from './packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs';
21
21
  export { ReactiveCache, reactiveCacheKey } from './packem_shared/ReactiveCache-BYlSGY0N.mjs';
22
- export { serveRelationFanout } from './packem_shared/serveRelationFanout-EZLD9-O3.mjs';
22
+ export { serveRelationFanout } from './packem_shared/serveRelationFanout-BuSmWawx.mjs';
23
23
  export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-DU-Y4-LJ.mjs';
24
24
  export { applyOnDelete, fanOutScalarCounts, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-BQ-8ZlZ1.mjs';
25
25
  export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs';
26
26
  export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
27
27
  export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
28
28
  export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs';
29
- export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-DDp_NomS.mjs';
29
+ export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-DlFX7CLL.mjs';
30
30
  export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs';
31
31
  export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-dDcFE1YZ.mjs';
32
32
  export { createSystemReader } from './packem_shared/createSystemReader-8CzSZP9V.mjs';
33
33
  export { ConflictError } from './packem_shared/ConflictError-C0STs6bU.mjs';
34
34
  export { hasTrigger, runTriggers } from './packem_shared/hasTrigger-5N6_Fx0A.mjs';
35
- export { compileWhereSql } from './packem_shared/compileWhereSql-CXrhFA3G.mjs';
35
+ export { compileWhereSql } from './packem_shared/compileWhereSql-MW_Lk8nJ.mjs';
36
36
  export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-DSycmnDf.mjs';
37
37
  export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BZsOqDXP.mjs';
38
38
  export { runShardMigrations } from './packem_shared/runShardMigrations-nIwoQeOK.mjs';
@@ -1,3 +1,5 @@
1
+ const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
2
+
1
3
  const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
2
4
  const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
3
5
  const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
@@ -95,7 +97,6 @@ const expandDocumentRows = (columns, rows) => {
95
97
  };
96
98
  const escapeLike = (value) => value.replaceAll(/[\\%_]/g, (character) => `\\${character}`);
97
99
  const isInternalTable = (name) => name.startsWith("sqlite_") || name.startsWith("_cf_") || name.startsWith("__miniflare") || name.startsWith("__lunora") || name.includes("__fts_");
98
- const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
99
100
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
100
101
  const countRows = (sql, quotedTable) => {
101
102
  const row = sql.exec(`SELECT COUNT(*) AS c FROM ${quotedTable}`).one();
@@ -19,11 +19,11 @@ import { guardWriter } from './RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs';
19
19
  import { createSystemReader } from './createSystemReader-8CzSZP9V.mjs';
20
20
  import { ConflictError } from './ConflictError-C0STs6bU.mjs';
21
21
  import { runTriggers } from './hasTrigger-5N6_Fx0A.mjs';
22
- import { compileWhereSql } from './compileWhereSql-CXrhFA3G.mjs';
22
+ import { compileWhereSql } from './compileWhereSql-MW_Lk8nJ.mjs';
23
23
  export { backfillAggregateIndexes, backfillRankIndexes } from './backfillAggregateIndexes-BZsOqDXP.mjs';
24
24
  export { C as CLIENT_WATERMARK_TABLE, G as GLOBAL_SHAPE_SNAPSHOT_TABLE, I as IDEMPOTENCY_TABLE, c as advanceClientWatermark, d as deleteGlobalShapeSnapshot, e as deleteGlobalShapeSnapshotsForConnection, m as migrateClientWatermark, b as migrateGlobalShapeSnapshot, r as readClientWatermark, f as readGlobalShapeSnapshot, g as readIdempotent, t as trimIdempotent, w as writeGlobalShapeSnapshot, h as writeIdempotent } from './ctx-db-idempotency-BdcNpvY4.mjs';
25
25
  export { runShardMigrations } from './runShardMigrations-nIwoQeOK.mjs';
26
- export { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-BQapRFjB.mjs';
26
+ export { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-0RaIOy7J.mjs';
27
27
 
28
28
  const rankIndexFieldsUnchanged = (index, previous, next) => {
29
29
  const fields = [...index.partitionBy ?? [], ...index.sortBy.map((key) => key.field), ...index.where ? Object.keys(index.where) : []];
@@ -5,7 +5,7 @@ import { recordAuthEvent, readAuthMetrics } from './AUTH_METRICS_BUCKETS_TABLE-C
5
5
  import { DATA_MIGRATION_STATE_TABLE, readMigrationStatus } from './DATA_MIGRATION_STATE_TABLE-DfPxn8I0.mjs';
6
6
  import { SCAN_DEP, createDependencyTracker, tableFromDepKey } from './SCAN_DEP-DLJF8dsj.mjs';
7
7
  import { readFunctionMetricsTotals, readFunctionMetricIndexHits, recordFunctionMetric, mergeScanAttribution, readFunctionMetrics, readFunctionMetricBuckets } from './FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
8
- import { createFanoutCounters, ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, selectMatchingIds, ADMIN_FUNCTIONS, findStorageReferences, listTables, summarizeSubscriptions, summarizeFanoutTopics, readTablePage, facetColumn, FLAGS_FUNCTION_PREFIX, recordFanoutPass, MAX_PAGE_SIZE } from './ADMIN_FUNCTIONS-fMl1vyt1.mjs';
8
+ import { createFanoutCounters, ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, selectMatchingIds, ADMIN_FUNCTIONS, findStorageReferences, listTables, summarizeSubscriptions, summarizeFanoutTopics, readTablePage, facetColumn, FLAGS_FUNCTION_PREFIX, recordFanoutPass, MAX_PAGE_SIZE } from './ADMIN_FUNCTIONS-BvGiv2u6.mjs';
9
9
  import { LogBuffer } from './LogBuffer-B_Ezju_N.mjs';
10
10
  import { recordCapturedMail, clearCapturedMail, readCapturedMail, MAIL_TABLE } from './MAIL_RETENTION-CPpgl-dX.mjs';
11
11
  import { readBookmark, armRestore } from './armRestore-BJk53Ro8.mjs';
@@ -17,7 +17,7 @@ import { runReadonlySql } from './MAX_SQL_ROWS-dDcFE1YZ.mjs';
17
17
  import { ConflictError } from './ConflictError-C0STs6bU.mjs';
18
18
  import { e as deleteGlobalShapeSnapshotsForConnection, g as readIdempotent, h as writeIdempotent, t as trimIdempotent, r as readClientWatermark, m as migrateClientWatermark, c as advanceClientWatermark, d as deleteGlobalShapeSnapshot, f as readGlobalShapeSnapshot, w as writeGlobalShapeSnapshot } from './ctx-db-idempotency-BdcNpvY4.mjs';
19
19
  import { CDC_LOG_TABLE, readCdcChanges, readCdcCursor, readCdcEpoch, minCdcSeq, bumpCdcEpoch } from './CDC_LOG_TABLE-DSycmnDf.mjs';
20
- import { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-BQapRFjB.mjs';
20
+ import { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-0RaIOy7J.mjs';
21
21
 
22
22
  const MAX_BATCH_ENTRIES = 500;
23
23
 
@@ -269,6 +269,30 @@ const buildPokeFrames = (parts, meta, options = {}) => {
269
269
 
270
270
  const DEFAULT_RELAY_FAN = 2;
271
271
  const DEFAULT_MAX_RELAYS = 8;
272
+ const RELAY_SECRET_KEY = "LUNORA_RELAY_SECRET";
273
+ const RELAY_SIGNATURE_HEADER = "x-lunora-relay-sig";
274
+ const relaySecretOf = (env) => {
275
+ const value = env?.[RELAY_SECRET_KEY];
276
+ return typeof value === "string" && value.length > 0 ? value : void 0;
277
+ };
278
+ const constantTimeEqual$1 = (a, b) => {
279
+ if (a.length !== b.length) {
280
+ return false;
281
+ }
282
+ let mismatch = 0;
283
+ for (let index = 0; index < a.length; index += 1) {
284
+ const charA = a.charCodeAt(index);
285
+ const charB = b.charCodeAt(index);
286
+ mismatch |= charA ^ charB;
287
+ }
288
+ return mismatch === 0;
289
+ };
290
+ const signRelayBody = async (secret, body) => {
291
+ const encoder = new TextEncoder();
292
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { hash: "SHA-256", name: "HMAC" }, false, ["sign"]);
293
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
294
+ return [...new Uint8Array(signature)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
295
+ };
272
296
  const envPositiveInt = (env, key, fallback) => {
273
297
  const raw = env?.[key];
274
298
  let parsed = Number.NaN;
@@ -305,9 +329,23 @@ class RelayLink {
305
329
  * to `relay.ts` without a case here is a COMPILE error, not a runtime mis-route.
306
330
  */
307
331
  async handleControl(request) {
332
+ let raw;
333
+ try {
334
+ raw = await request.text();
335
+ } catch {
336
+ return new Response("bad request", { status: 400 });
337
+ }
338
+ const secret = relaySecretOf(this.host.env());
339
+ if (secret !== void 0) {
340
+ const supplied = request.headers.get(RELAY_SIGNATURE_HEADER);
341
+ const expected = await signRelayBody(secret, raw);
342
+ if (supplied === null || !constantTimeEqual$1(supplied, expected)) {
343
+ return new Response("forbidden", { status: 403 });
344
+ }
345
+ }
308
346
  let message;
309
347
  try {
310
- message = await request.json();
348
+ message = JSON.parse(raw);
311
349
  } catch {
312
350
  return new Response("bad request", { status: 400 });
313
351
  }
@@ -372,10 +410,16 @@ class RelayLink {
372
410
  return void 0;
373
411
  }
374
412
  const stub = typeof namespace.getByName === "function" ? namespace.getByName(targetName) : namespace.get(namespace.idFromName(targetName));
413
+ const body = JSON.stringify(message);
414
+ const headers = { "content-type": "application/json", "x-lunora-shard-binding": this.host.shardBinding() ?? "" };
415
+ const secret = relaySecretOf(this.host.env());
416
+ if (secret !== void 0) {
417
+ headers[RELAY_SIGNATURE_HEADER] = await signRelayBody(secret, body);
418
+ }
375
419
  try {
376
420
  return await stub.fetch("https://relay.internal/_lunora/relay", {
377
- body: JSON.stringify(message),
378
- headers: { "content-type": "application/json", "x-lunora-shard-binding": this.host.shardBinding() ?? "" },
421
+ body,
422
+ headers,
379
423
  method: "POST"
380
424
  });
381
425
  } catch {
@@ -679,8 +723,9 @@ class OwnerRelay extends RelayLink {
679
723
  * sound against the cross-identity row-leak (review). Resolves under the anonymous
680
724
  * multicast identity (the base) plus two `Proxy`-backed identities that return a
681
725
  * distinct value for ANY accessed claim, requires all to agree on table + where +
682
- * columns, rejects any table with an RLS read policy or a masked projected column,
683
- * and fails closed if the claims are enumerated (a wholesale copy the proxy can't
726
+ * columns, rejects any table with an RLS read policy or ANY masked column
727
+ * defined (even if unprojected see {@link OwnerRelay.tableHasAnyMask}), and
728
+ * fails closed if the claims are enumerated (a wholesale copy the proxy can't
684
729
  * differentiate).
685
730
  */
686
731
  probeShapeRelayUniform(name, args) {
@@ -696,7 +741,7 @@ class OwnerRelay extends RelayLink {
696
741
  if (this.host.rlsMetadata().policies.some((policy) => policy.on === "read" && policy.table === base.table)) {
697
742
  return false;
698
743
  }
699
- if (this.shapeColumnsMasked(base.table, base.columns)) {
744
+ if (this.tableHasAnyMask(base.table)) {
700
745
  return false;
701
746
  }
702
747
  const baseWhere = stableStringify(base.effectiveWhere);
@@ -734,17 +779,18 @@ class OwnerRelay extends RelayLink {
734
779
  });
735
780
  return matches && !enumerated;
736
781
  }
737
- /** Whether any column the shape projects from `table` is masked — a masked value is identity-dependent, so the shape can't be relay-uniform. */
738
- shapeColumnsMasked(table, columns) {
739
- const masked = this.host.maskMetadata().columns.filter((entry) => entry.table === table);
740
- if (masked.length === 0) {
741
- return false;
742
- }
743
- if (columns === void 0) {
744
- return true;
745
- }
746
- const projected = new Set(columns);
747
- return masked.some((entry) => projected.has(entry.column));
782
+ /**
783
+ * Whether `table` has ANY masked column defined (L7). Deliberately conservative:
784
+ * we disqualify a shape from relay-multicast if its table declares any mask at
785
+ * all, even when the current query doesn't project the masked column. A masked
786
+ * value is identity-dependent, and keying uniformity off the *projected* set
787
+ * meant a later column addition (or a `select` change) could silently widen a
788
+ * cohort to include an identity-dependent value. Refusing on any table-level
789
+ * mask removes that footgun; the cost is a few extra shapes falling back to the
790
+ * per-identity path, which is always correct.
791
+ */
792
+ tableHasAnyMask(table) {
793
+ return this.host.maskMetadata().columns.some((entry) => entry.table === table);
748
794
  }
749
795
  }
750
796
  class RelayMember extends RelayLink {
@@ -2785,7 +2831,19 @@ class ShardDO {
2785
2831
  */
2786
2832
  // eslint-disable-next-line class-methods-use-this -- base-class override hook: the codegen subclass overrides this with the statically-discovered feature flags
2787
2833
  studioFeatures() {
2788
- return { flags: false, mail: false, payments: false, queues: false, scheduler: false, storage: false, vectors: false, workflows: false };
2834
+ return {
2835
+ analytics: false,
2836
+ auth: false,
2837
+ flags: false,
2838
+ kv: false,
2839
+ mail: false,
2840
+ payments: false,
2841
+ queues: false,
2842
+ scheduler: false,
2843
+ storage: false,
2844
+ vectors: false,
2845
+ workflows: false
2846
+ };
2789
2847
  }
2790
2848
  /**
2791
2849
  * Evaluate every statically-discovered feature flag under `context` for the
@@ -11,9 +11,10 @@ const isOperatorObject = (value) => {
11
11
  const keys = Object.keys(value);
12
12
  return keys.length > 0 && keys.every((key) => OPERATOR_KEY_SET.has(key));
13
13
  };
14
+ const escapeLikeTerm = (value) => typeof value === "string" ? value.replaceAll(/[\\%_]/g, (character) => `\\${character}`) : value;
14
15
  const compileContains = (reference, value, strategy) => {
15
- const term = sql`${strategy.serialize(value)}`;
16
- return strategy.likeContains ? strategy.likeContains(reference, term) : sql`${reference} LIKE '%' || ${term} || '%'`;
16
+ const term = sql`${strategy.serialize(escapeLikeTerm(value))}`;
17
+ return strategy.likeContains ? strategy.likeContains(reference, term) : sql`${reference} LIKE '%' || ${term} || '%' ESCAPE '\\'`;
17
18
  };
18
19
  const compileComparator = (reference, operator, comparator, value, strategy) => {
19
20
  if (value === null) {
@@ -1,7 +1,7 @@
1
1
  import { sql } from 'drizzle-orm';
2
2
  import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
3
3
  import { D as DOC_COLUMN, r as rowToDocument, j as jsonPathSql } from './do-sql-BCHCWtrD.mjs';
4
- import { compileWhereSql } from './compileWhereSql-CXrhFA3G.mjs';
4
+ import { compileWhereSql } from './compileWhereSql-MW_Lk8nJ.mjs';
5
5
  import { s as serializeSqlValue } from './serialize-sql-BlRUoiQe.mjs';
6
6
 
7
7
  const shapeWhereStrategy = { fieldRef: jsonPathSql, serialize: serializeSqlValue };
@@ -1,4 +1,4 @@
1
- import { runExternalSourceTick } from './materializeExternalRows-uKBx9H0s.mjs';
1
+ import { runExternalSourceTick } from './materializeExternalRows-Bj6EXy50.mjs';
2
2
 
3
3
  const liftSourceId = (row, options = {}) => {
4
4
  const { idColumn = "id", map } = options;
@@ -1,5 +1,5 @@
1
1
  import { applyCdcChanges } from './CDC_LOG_TABLE-DSycmnDf.mjs';
2
- import { s as selectShapeRows } from './ctx-db-shapes-BQapRFjB.mjs';
2
+ import { s as selectShapeRows } from './ctx-db-shapes-0RaIOy7J.mjs';
3
3
  import { diffExternalSource, projectExternalSourceRow } from './diffExternalSource-Cx9HUPJj.mjs';
4
4
  import { stableStringify } from './stableStringify-MydiuScU.mjs';
5
5
 
@@ -1,4 +1,4 @@
1
- import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-fMl1vyt1.mjs';
1
+ import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-BvGiv2u6.mjs';
2
2
 
3
3
  const serveRelationFanout = async (schema, database, functionPath, args) => {
4
4
  const table = typeof args["table"] === "string" ? args["table"] : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/do",
3
- "version": "1.0.0-alpha.17",
3
+ "version": "1.0.0-alpha.19",
4
4
  "description": "Lunora Durable Objects: ShardDO (SQLite, OCC, hibernated WebSocket subscriptions) and SessionDO",
5
5
  "keywords": [
6
6
  "cloudflare",