@sixb/sqlite 0.1.7 → 0.1.8

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.
Files changed (49) hide show
  1. package/dist/execution-storage.d.ts +3 -2
  2. package/dist/execution-storage.d.ts.map +1 -1
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1492 -380
  6. package/dist/index.js.map +16 -10
  7. package/dist/migrations.d.ts.map +1 -1
  8. package/dist/objects/index.d.ts +3 -0
  9. package/dist/objects/index.d.ts.map +1 -0
  10. package/dist/{object-query-compiler.d.ts → objects/query-compiler.d.ts} +16 -1
  11. package/dist/objects/query-compiler.d.ts.map +1 -0
  12. package/dist/objects/read-scope.d.ts +17 -0
  13. package/dist/objects/read-scope.d.ts.map +1 -0
  14. package/dist/objects/reader.d.ts +51 -0
  15. package/dist/objects/reader.d.ts.map +1 -0
  16. package/dist/objects/rows.d.ts +33 -0
  17. package/dist/objects/rows.d.ts.map +1 -0
  18. package/dist/objects/selected-reader.d.ts +9 -0
  19. package/dist/objects/selected-reader.d.ts.map +1 -0
  20. package/dist/{object-storage.d.ts → objects/storage.d.ts} +12 -9
  21. package/dist/objects/storage.d.ts.map +1 -0
  22. package/dist/share-grant-storage.d.ts +20 -0
  23. package/dist/share-grant-storage.d.ts.map +1 -0
  24. package/dist/share-session-storage.d.ts +19 -0
  25. package/dist/share-session-storage.d.ts.map +1 -0
  26. package/dist/storage-errors.d.ts +1 -0
  27. package/dist/storage-errors.d.ts.map +1 -1
  28. package/dist/transactions.d.ts +8 -0
  29. package/dist/transactions.d.ts.map +1 -1
  30. package/package.json +3 -3
  31. package/src/execution-storage.ts +22 -3
  32. package/src/index.ts +18 -3
  33. package/src/migrations/035-share-grants.sql +66 -0
  34. package/src/migrations/036-share-sessions.sql +226 -0
  35. package/src/migrations.ts +4 -0
  36. package/src/objects/index.ts +2 -0
  37. package/src/{object-query-compiler.ts → objects/query-compiler.ts} +104 -54
  38. package/src/objects/read-scope.ts +316 -0
  39. package/src/objects/reader.ts +526 -0
  40. package/src/objects/rows.ts +123 -0
  41. package/src/objects/selected-reader.ts +119 -0
  42. package/src/objects/storage.ts +296 -0
  43. package/src/share-grant-storage.ts +252 -0
  44. package/src/share-session-storage.ts +189 -0
  45. package/src/storage-errors.ts +4 -0
  46. package/src/transactions.ts +32 -0
  47. package/dist/object-query-compiler.d.ts.map +0 -1
  48. package/dist/object-storage.d.ts.map +0 -1
  49. package/src/object-storage.ts +0 -709
@@ -0,0 +1,226 @@
1
+ -- Shared sessions and their execution provenance.
2
+ CREATE TABLE share_sessions (
3
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
4
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
5
+ grant_id TEXT NOT NULL CHECK (length(trim(grant_id)) > 0),
6
+ token_hash TEXT NOT NULL CHECK (
7
+ length(token_hash) = 64
8
+ AND token_hash = lower(token_hash)
9
+ AND token_hash NOT GLOB '*[^0-9a-f]*'
10
+ ),
11
+ created_at TEXT NOT NULL,
12
+ expires_at TEXT NOT NULL,
13
+ absolute_expires_at TEXT NOT NULL,
14
+ revoked_at TEXT,
15
+ PRIMARY KEY (project_id, id),
16
+ UNIQUE (project_id, id, grant_id),
17
+ UNIQUE (project_id, token_hash),
18
+ FOREIGN KEY (project_id, grant_id)
19
+ REFERENCES share_grants (project_id, id),
20
+ CHECK (expires_at > created_at),
21
+ CHECK (absolute_expires_at > created_at),
22
+ CHECK (expires_at <= absolute_expires_at),
23
+ CHECK (revoked_at IS NULL OR revoked_at >= created_at)
24
+ );
25
+
26
+ CREATE INDEX idx_share_sessions_grant
27
+ ON share_sessions (project_id, grant_id, created_at DESC, id DESC);
28
+
29
+ CREATE INDEX idx_share_sessions_expiry
30
+ ON share_sessions (project_id, expires_at)
31
+ WHERE revoked_at IS NULL;
32
+
33
+ -- Persist shared-session provenance on request executions without fabricating a principal.
34
+ CREATE TABLE executions_v2 (
35
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
36
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
37
+ executor_kind TEXT NOT NULL CHECK (
38
+ executor_kind IN (
39
+ 'request', 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow',
40
+ 'agent', 'kernel'
41
+ )
42
+ ),
43
+ executor_id TEXT NOT NULL CHECK (length(trim(executor_id)) > 0),
44
+ source_kind TEXT NOT NULL CHECK (
45
+ source_kind IN ('http', 'webhook', 'schedule', 'event', 'datasetVersion', 'execution')
46
+ ),
47
+ source_id TEXT NOT NULL CHECK (length(trim(source_id)) > 0),
48
+ requested_by_user_id TEXT,
49
+ requested_by_service_account_id TEXT,
50
+ correlation_id TEXT NOT NULL CHECK (length(trim(correlation_id)) > 0),
51
+ parent_execution_id TEXT,
52
+ authority_kind TEXT NOT NULL CHECK (
53
+ authority_kind IN ('principal', 'trustedPrimitive', 'delegated', 'kernel', 'disabled')
54
+ ),
55
+ authority_user_id TEXT,
56
+ authority_service_account_id TEXT,
57
+ authority_session_id TEXT,
58
+ authority_access_token_id TEXT,
59
+ authority_primitive_kind TEXT CHECK (
60
+ authority_primitive_kind IS NULL
61
+ OR authority_primitive_kind IN (
62
+ 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow'
63
+ )
64
+ ),
65
+ authority_primitive_id TEXT,
66
+ authority_kernel_operation TEXT CHECK (
67
+ authority_kernel_operation IS NULL OR authority_kernel_operation = 'ontology.recover'
68
+ ),
69
+ authority_delegation_kind TEXT CHECK (
70
+ authority_delegation_kind IS NULL OR authority_delegation_kind = 'share'
71
+ ),
72
+ authority_delegation_id TEXT,
73
+ authority_delegation_session_id TEXT,
74
+ created_at TEXT NOT NULL,
75
+ PRIMARY KEY (project_id, id),
76
+ FOREIGN KEY (project_id, parent_execution_id) REFERENCES executions (project_id, id),
77
+ FOREIGN KEY (project_id, requested_by_user_id) REFERENCES auth_users (project_id, id),
78
+ FOREIGN KEY (project_id, requested_by_service_account_id)
79
+ REFERENCES auth_service_accounts (project_id, id),
80
+ FOREIGN KEY (project_id, authority_user_id) REFERENCES auth_users (project_id, id),
81
+ FOREIGN KEY (project_id, authority_service_account_id)
82
+ REFERENCES auth_service_accounts (project_id, id),
83
+ FOREIGN KEY (project_id, authority_session_id) REFERENCES auth_sessions (project_id, id),
84
+ FOREIGN KEY (project_id, authority_access_token_id)
85
+ REFERENCES auth_access_tokens (project_id, id),
86
+ FOREIGN KEY (
87
+ project_id, authority_delegation_session_id, authority_delegation_id
88
+ ) REFERENCES share_sessions (project_id, id, grant_id),
89
+ CHECK ((requested_by_user_id IS NOT NULL) + (requested_by_service_account_id IS NOT NULL) <= 1),
90
+ CHECK (
91
+ (source_kind = 'execution' AND parent_execution_id = source_id)
92
+ OR (source_kind <> 'execution' AND parent_execution_id IS NULL)
93
+ ),
94
+ CHECK (
95
+ (
96
+ authority_kind = 'principal'
97
+ AND (authority_user_id IS NOT NULL) + (authority_service_account_id IS NOT NULL) = 1
98
+ AND (authority_session_id IS NOT NULL) + (authority_access_token_id IS NOT NULL) <= 1
99
+ AND (authority_session_id IS NULL OR authority_user_id IS NOT NULL)
100
+ AND authority_primitive_kind IS NULL
101
+ AND authority_primitive_id IS NULL
102
+ AND authority_kernel_operation IS NULL
103
+ AND authority_delegation_kind IS NULL
104
+ AND authority_delegation_id IS NULL
105
+ AND authority_delegation_session_id IS NULL
106
+ )
107
+ OR (
108
+ authority_kind = 'trustedPrimitive'
109
+ AND authority_user_id IS NULL
110
+ AND authority_service_account_id IS NULL
111
+ AND authority_session_id IS NULL
112
+ AND authority_access_token_id IS NULL
113
+ AND authority_primitive_kind IS NOT NULL
114
+ AND authority_primitive_id IS NOT NULL
115
+ AND length(trim(authority_primitive_id)) > 0
116
+ AND authority_kernel_operation IS NULL
117
+ AND authority_delegation_kind IS NULL
118
+ AND authority_delegation_id IS NULL
119
+ AND authority_delegation_session_id IS NULL
120
+ )
121
+ OR (
122
+ authority_kind = 'delegated'
123
+ AND authority_user_id IS NULL
124
+ AND authority_service_account_id IS NULL
125
+ AND authority_session_id IS NULL
126
+ AND authority_access_token_id IS NULL
127
+ AND authority_primitive_kind IS NULL
128
+ AND authority_primitive_id IS NULL
129
+ AND authority_kernel_operation IS NULL
130
+ AND authority_delegation_kind IS NOT NULL
131
+ AND authority_delegation_kind = 'share'
132
+ AND authority_delegation_id IS NOT NULL
133
+ AND length(trim(authority_delegation_id)) > 0
134
+ AND authority_delegation_session_id IS NOT NULL
135
+ AND length(trim(authority_delegation_session_id)) > 0
136
+ )
137
+ OR (
138
+ authority_kind = 'kernel'
139
+ AND authority_user_id IS NULL
140
+ AND authority_service_account_id IS NULL
141
+ AND authority_session_id IS NULL
142
+ AND authority_access_token_id IS NULL
143
+ AND authority_primitive_kind IS NULL
144
+ AND authority_primitive_id IS NULL
145
+ AND authority_kernel_operation IS NOT NULL
146
+ AND authority_delegation_kind IS NULL
147
+ AND authority_delegation_id IS NULL
148
+ AND authority_delegation_session_id IS NULL
149
+ )
150
+ OR (
151
+ authority_kind = 'disabled'
152
+ AND authority_user_id IS NULL
153
+ AND authority_service_account_id IS NULL
154
+ AND authority_session_id IS NULL
155
+ AND authority_access_token_id IS NULL
156
+ AND authority_primitive_kind IS NULL
157
+ AND authority_primitive_id IS NULL
158
+ AND authority_kernel_operation IS NULL
159
+ AND authority_delegation_kind IS NULL
160
+ AND authority_delegation_id IS NULL
161
+ AND authority_delegation_session_id IS NULL
162
+ )
163
+ ),
164
+ CHECK (
165
+ (
166
+ executor_kind = 'request'
167
+ AND source_kind = 'http'
168
+ AND executor_id = source_id
169
+ AND (
170
+ (
171
+ authority_kind = 'principal'
172
+ AND requested_by_user_id IS authority_user_id
173
+ AND requested_by_service_account_id IS authority_service_account_id
174
+ )
175
+ OR (
176
+ authority_kind IN ('delegated', 'disabled')
177
+ AND requested_by_user_id IS NULL
178
+ AND requested_by_service_account_id IS NULL
179
+ )
180
+ )
181
+ )
182
+ OR (
183
+ executor_kind IN ('action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow')
184
+ AND authority_kind = 'trustedPrimitive'
185
+ AND executor_kind = authority_primitive_kind
186
+ )
187
+ OR (
188
+ executor_kind = 'agent'
189
+ AND source_kind = 'execution'
190
+ AND (
191
+ (
192
+ authority_kind = 'principal'
193
+ AND authority_service_account_id IS NOT NULL
194
+ AND authority_session_id IS NULL
195
+ AND authority_access_token_id IS NULL
196
+ )
197
+ OR (
198
+ authority_kind = 'principal'
199
+ AND authority_user_id IS NOT NULL
200
+ AND (authority_session_id IS NOT NULL) + (authority_access_token_id IS NOT NULL) = 1
201
+ )
202
+ OR authority_kind = 'disabled'
203
+ )
204
+ )
205
+ OR (executor_kind = 'kernel' AND authority_kind = 'kernel')
206
+ )
207
+ );
208
+
209
+ INSERT INTO executions_v2 (
210
+ project_id, id, executor_kind, executor_id, source_kind, source_id,
211
+ requested_by_user_id, requested_by_service_account_id, correlation_id, parent_execution_id,
212
+ authority_kind, authority_user_id, authority_service_account_id, authority_session_id,
213
+ authority_access_token_id, authority_primitive_kind, authority_primitive_id,
214
+ authority_kernel_operation, authority_delegation_kind, authority_delegation_id,
215
+ authority_delegation_session_id, created_at
216
+ )
217
+ SELECT
218
+ project_id, id, executor_kind, executor_id, source_kind, source_id,
219
+ requested_by_user_id, requested_by_service_account_id, correlation_id, parent_execution_id,
220
+ authority_kind, authority_user_id, authority_service_account_id, authority_session_id,
221
+ authority_access_token_id, authority_primitive_kind, authority_primitive_id,
222
+ authority_kernel_operation, NULL, NULL, NULL, created_at
223
+ FROM executions;
224
+
225
+ DROP TABLE executions;
226
+ ALTER TABLE executions_v2 RENAME TO executions;
package/src/migrations.ts CHANGED
@@ -88,6 +88,8 @@ import retireAgentDefinitionsSql from "./migrations/033-retire-agent-definitions
88
88
  import deviceAuthorizationsSql from "./migrations/034-device-authorizations.sql" with {
89
89
  type: "text",
90
90
  }
91
+ import shareGrantsSql from "./migrations/035-share-grants.sql" with { type: "text" }
92
+ import shareSessionsSql from "./migrations/036-share-sessions.sql" with { type: "text" }
91
93
 
92
94
  const MIGRATIONS_TABLE_SQL = `
93
95
  CREATE TABLE IF NOT EXISTS sixb_migrations (
@@ -177,6 +179,8 @@ export const sqliteStorageMigrations = defineMigrations({
177
179
  sqliteSql("032-conversation-run-spec", conversationRunSpecSql),
178
180
  sqliteSql("033-retire-agent-definitions", retireAgentDefinitionsSql),
179
181
  sqliteSql("034-device-authorizations", deviceAuthorizationsSql),
182
+ sqliteSql("035-share-grants", shareGrantsSql),
183
+ sqliteSql("036-share-sessions", shareSessionsSql),
180
184
  ],
181
185
  })
182
186
 
@@ -0,0 +1,2 @@
1
+ export type { SqliteObjectStorageOptions } from "./storage"
2
+ export { SqliteObjectStorage } from "./storage"
@@ -36,6 +36,7 @@ interface CompiledHasMoreProbe {
36
36
 
37
37
  interface CompileContext {
38
38
  probeLimit: boolean
39
+ source: SqliteObjectQuerySource
39
40
  }
40
41
 
41
42
  interface CompiledPredicate {
@@ -74,7 +75,6 @@ interface EncodedCursorValue {
74
75
  }
75
76
 
76
77
  const PAGE_TOKEN_PREFIX = "keyset:"
77
- const exactContext: CompileContext = { probeLimit: false }
78
78
 
79
79
  // Fallback fanout cap for a "many" expansion that arrives without an explicit
80
80
  // limit. The core executor normally bakes a limit in before pushdown; this only
@@ -84,10 +84,37 @@ const DEFAULT_EXPANSION_FANOUT = 1_000
84
84
  export function compileObjectQuery(
85
85
  projectId: string,
86
86
  query: ObjectQuery,
87
- options: { includeTotal?: boolean } = {}
87
+ options: { includeTotal?: boolean; source?: SqliteObjectQuerySource } = {}
88
88
  ): CompiledObjectQuery {
89
- const ctx: CompileContext = { probeLimit: options.includeTotal === false }
90
- return compileObjectQueryInternal(projectId, query, ctx)
89
+ const source = options.source ?? DEFAULT_OBJECT_QUERY_SOURCE
90
+ const ctx: CompileContext = { probeLimit: options.includeTotal === false, source }
91
+ return source.wrapQuery(compileObjectQueryInternal(projectId, query, ctx))
92
+ }
93
+
94
+ /** Physical relations used by the object-query compiler. */
95
+ export interface SqliteObjectQuerySource {
96
+ readonly objectsTable: string
97
+ readonly linksTable: string
98
+ /** Wrap a terminal SELECT without its own WITH clause; the selected source owns the only WITH. */
99
+ wrapStatement(
100
+ sql: string,
101
+ args?: readonly SqliteValue[]
102
+ ): {
103
+ readonly sql: string
104
+ readonly args: readonly SqliteValue[]
105
+ }
106
+ wrapQuery(query: CompiledObjectQuery): CompiledObjectQuery
107
+ }
108
+
109
+ export const DEFAULT_OBJECT_QUERY_SOURCE: SqliteObjectQuerySource = {
110
+ objectsTable: "objects",
111
+ linksTable: "links",
112
+ wrapStatement: (sql, args = []) => ({ sql, args: [...args] }),
113
+ wrapQuery: (query) => query,
114
+ }
115
+
116
+ function exactContext(ctx: CompileContext): CompileContext {
117
+ return ctx.probeLimit ? { ...ctx, probeLimit: false } : ctx
91
118
  }
92
119
 
93
120
  function compileObjectQueryInternal(
@@ -97,27 +124,28 @@ function compileObjectQueryInternal(
97
124
  ): CompiledObjectQuery {
98
125
  switch (query.kind) {
99
126
  case "start":
100
- return compileStart(projectId, query)
127
+ return compileStart(projectId, query, ctx)
101
128
  case "refs":
102
- return compileRefs(projectId, query)
129
+ return compileRefs(projectId, query, ctx)
103
130
  case "filter":
104
- return compileFilter(projectId, query.input, query.predicate)
131
+ return compileFilter(projectId, query.input, query.predicate, ctx)
105
132
  case "sort":
106
133
  return compileSort(projectId, query.input, query.fields, ctx)
107
134
  case "limit":
108
135
  return compileLimit(projectId, query.input, query.limit, ctx)
109
136
  case "page":
110
- return compilePage(projectId, query.input, query.pageSize, query.pageToken)
137
+ return compilePage(projectId, query.input, query.pageSize, query.pageToken, ctx)
111
138
  case "traverse":
112
139
  return compileTraversal(
113
140
  projectId,
114
141
  query.input,
115
142
  query.linkId,
116
143
  query.direction,
117
- query.sourceObjectTypeId
144
+ query.sourceObjectTypeId,
145
+ ctx
118
146
  )
119
147
  case "set":
120
- return compileSet(projectId, query.op, query.inputs)
148
+ return compileSet(projectId, query.op, query.inputs, ctx)
121
149
  case "project":
122
150
  return compileProject(projectId, query.input, query.properties, ctx)
123
151
  case "text":
@@ -126,10 +154,11 @@ function compileObjectQueryInternal(
126
154
  query.input,
127
155
  query.query,
128
156
  query.fields,
129
- query.fieldsByObjectType
157
+ query.fieldsByObjectType,
158
+ ctx
130
159
  )
131
160
  case "expand":
132
- return compileExpand(projectId, query.input, query.expansions)
161
+ return compileExpand(projectId, query.input, query.expansions, ctx)
133
162
  case "vector":
134
163
  throw new Error(`[Sixb] SQLite object storage does not support query node '${query.kind}'`)
135
164
  }
@@ -137,7 +166,8 @@ function compileObjectQueryInternal(
137
166
 
138
167
  function compileStart(
139
168
  projectId: string,
140
- query: Extract<ObjectQuery, { kind: "start" }>
169
+ query: Extract<ObjectQuery, { kind: "start" }>,
170
+ ctx: CompileContext
141
171
  ): CompiledObjectQuery {
142
172
  if (query.includeSubtypes === true) {
143
173
  throw new Error("[Sixb] SQLite object storage does not support start.includeSubtypes")
@@ -146,7 +176,7 @@ function compileStart(
146
176
  const order = compileOrder(identityOrderFields())
147
177
  const sql = `
148
178
  SELECT *, properties AS _cursor_properties
149
- FROM objects
179
+ FROM ${ctx.source.objectsTable}
150
180
  WHERE project_id = ? AND object_type_id = ?
151
181
  ORDER BY ${order.sql}
152
182
  `
@@ -155,7 +185,7 @@ function compileStart(
155
185
  return {
156
186
  sql,
157
187
  args,
158
- totalSql: "SELECT COUNT(*) as total FROM objects WHERE project_id = ? AND object_type_id = ?",
188
+ totalSql: `SELECT COUNT(*) as total FROM ${ctx.source.objectsTable} WHERE project_id = ? AND object_type_id = ?`,
159
189
  totalArgs: [projectId, query.objectTypeId],
160
190
  order,
161
191
  hasMore: () => false,
@@ -166,7 +196,8 @@ function compileStart(
166
196
 
167
197
  function compileRefs(
168
198
  projectId: string,
169
- query: Extract<ObjectQuery, { kind: "refs" }>
199
+ query: Extract<ObjectQuery, { kind: "refs" }>,
200
+ ctx: CompileContext
170
201
  ): CompiledObjectQuery {
171
202
  if (query.refs.length === 0) {
172
203
  throw new Error("[Sixb] SQLite object storage requires at least one ref")
@@ -182,10 +213,9 @@ function compileRefs(
182
213
  FROM json_each(?) AS ref
183
214
  `
184
215
  const sql = `
185
- WITH requested AS (${requested})
186
216
  SELECT selected.*, selected.properties AS _cursor_properties
187
- FROM requested
188
- JOIN objects AS selected
217
+ FROM (${requested}) AS requested
218
+ JOIN ${ctx.source.objectsTable} AS selected
189
219
  ON selected.project_id = ?
190
220
  AND selected.object_type_id = requested.object_type_id
191
221
  AND selected.primary_id = requested.primary_id
@@ -196,10 +226,9 @@ function compileRefs(
196
226
  sql,
197
227
  args: [refsJson, projectId, ...selectedOrder.args],
198
228
  totalSql: `
199
- WITH requested AS (${requested})
200
229
  SELECT COUNT(*) AS total
201
- FROM requested
202
- JOIN objects AS selected
230
+ FROM (${requested}) AS requested
231
+ JOIN ${ctx.source.objectsTable} AS selected
203
232
  ON selected.project_id = ?
204
233
  AND selected.object_type_id = requested.object_type_id
205
234
  AND selected.primary_id = requested.primary_id
@@ -215,18 +244,20 @@ function compileRefs(
215
244
  function compileFilter(
216
245
  projectId: string,
217
246
  inputQuery: ObjectQuery,
218
- predicateNode: ObjectQueryPredicate
247
+ predicateNode: ObjectQueryPredicate,
248
+ ctx: CompileContext
219
249
  ): CompiledObjectQuery {
220
250
  const predicate = compilePredicate(predicateNode)
221
- return compileWhere(projectId, inputQuery, predicate)
251
+ return compileWhere(projectId, inputQuery, predicate, ctx)
222
252
  }
223
253
 
224
254
  function compileWhere(
225
255
  projectId: string,
226
256
  inputQuery: ObjectQuery,
227
- predicate: CompiledPredicate
257
+ predicate: CompiledPredicate,
258
+ ctx: CompileContext
228
259
  ): CompiledObjectQuery {
229
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext)
260
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx))
230
261
  const sql = `
231
262
  SELECT *
232
263
  FROM (${input.sql}) AS input
@@ -255,7 +286,8 @@ function compileText(
255
286
  inputQuery: ObjectQuery,
256
287
  query: string,
257
288
  fields: readonly string[] | undefined,
258
- fieldsByObjectType: Readonly<Record<string, readonly string[]>> | undefined
289
+ fieldsByObjectType: Readonly<Record<string, readonly string[]>> | undefined,
290
+ ctx: CompileContext
259
291
  ): CompiledObjectQuery {
260
292
  const predicate =
261
293
  fields && fields.length > 0
@@ -265,7 +297,7 @@ function compileText(
265
297
  if (!predicate) {
266
298
  throw new Error("[Sixb] SQLite object text search requires fields or resolved text defaults")
267
299
  }
268
- return compileWhere(projectId, inputQuery, predicate)
300
+ return compileWhere(projectId, inputQuery, predicate, ctx)
269
301
  }
270
302
 
271
303
  function compileSort(
@@ -274,7 +306,7 @@ function compileSort(
274
306
  fields: readonly ObjectQuerySortField[],
275
307
  ctx: CompileContext
276
308
  ): CompiledObjectQuery {
277
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext)
309
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx))
278
310
  const probeInput =
279
311
  ctx.probeLimit && needsLimitProbe(inputQuery)
280
312
  ? compileObjectQueryInternal(projectId, inputQuery, ctx)
@@ -313,7 +345,7 @@ function compileLimit(
313
345
  rawLimit: number,
314
346
  ctx: CompileContext
315
347
  ): CompiledObjectQuery {
316
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext)
348
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx))
317
349
  const limit = Math.max(0, rawLimit)
318
350
  const rowLimit = ctx.probeLimit ? limit + 1 : limit
319
351
 
@@ -342,9 +374,10 @@ function compilePage(
342
374
  projectId: string,
343
375
  inputQuery: ObjectQuery,
344
376
  rawPageSize: number,
345
- pageToken: string | undefined
377
+ pageToken: string | undefined,
378
+ ctx: CompileContext
346
379
  ): CompiledObjectQuery {
347
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext)
380
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx))
348
381
  const pageSize = Math.max(0, rawPageSize)
349
382
  const cursor = pageToken ? decodePageToken(pageToken, input.order.fields) : undefined
350
383
  const cursorPredicate = cursor
@@ -381,30 +414,31 @@ function compileTraversal(
381
414
  inputQuery: ObjectQuery,
382
415
  linkId: string,
383
416
  direction: "outgoing" | "incoming",
384
- sourceObjectTypeId?: string
417
+ sourceObjectTypeId: string | undefined,
418
+ ctx: CompileContext
385
419
  ): CompiledObjectQuery {
386
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext)
420
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx))
387
421
  const outputAlias = direction === "outgoing" ? "target_object" : "source_object"
388
422
  const joinSql =
389
423
  direction === "outgoing"
390
424
  ? `
391
- JOIN links AS edge
425
+ JOIN ${ctx.source.linksTable} AS edge
392
426
  ON edge.project_id = input.project_id
393
427
  AND edge.source_type_id = input.object_type_id
394
428
  AND edge.source_id = input.primary_id
395
429
  AND edge.link_id = ?
396
- JOIN objects AS target_object
430
+ JOIN ${ctx.source.objectsTable} AS target_object
397
431
  ON target_object.project_id = edge.project_id
398
432
  AND target_object.object_type_id = edge.target_type_id
399
433
  AND target_object.primary_id = edge.target_id
400
434
  `
401
435
  : `
402
- JOIN links AS edge
436
+ JOIN ${ctx.source.linksTable} AS edge
403
437
  ON edge.project_id = input.project_id
404
438
  AND edge.target_type_id = input.object_type_id
405
439
  AND edge.target_id = input.primary_id
406
440
  AND edge.link_id = ?${sourceObjectTypeId === undefined ? "" : "\n AND edge.source_type_id = ?"}
407
- JOIN objects AS source_object
441
+ JOIN ${ctx.source.objectsTable} AS source_object
408
442
  ON source_object.project_id = edge.project_id
409
443
  AND source_object.object_type_id = edge.source_type_id
410
444
  AND source_object.primary_id = edge.source_id
@@ -451,13 +485,15 @@ interface ExpansionParent {
451
485
  function compileExpand(
452
486
  projectId: string,
453
487
  inputQuery: ObjectQuery,
454
- expansions: readonly ObjectExpansion[]
488
+ expansions: readonly ObjectExpansion[],
489
+ ctx: CompileContext
455
490
  ): CompiledObjectQuery {
456
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext)
491
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx))
457
492
  const expand = compileExpansionsObject(
458
493
  expansions,
459
494
  { project: "input.project_id", type: "input.object_type_id", id: "input.primary_id" },
460
- ""
495
+ "",
496
+ ctx
461
497
  )
462
498
  const inputOrder = compileOrder(input.order.fields, "input", "input._cursor_properties")
463
499
  const sql = `
@@ -487,13 +523,14 @@ function compileExpand(
487
523
  function compileExpansionsObject(
488
524
  expansions: readonly ObjectExpansion[],
489
525
  parent: ExpansionParent,
490
- pathPrefix: string
526
+ pathPrefix: string,
527
+ ctx: CompileContext
491
528
  ): CompiledPredicate {
492
529
  const parts: string[] = []
493
530
  const args: SqliteValue[] = []
494
531
  expansions.forEach((expansion, index) => {
495
532
  const path = pathPrefix === "" ? `${index}` : `${pathPrefix}_${index}`
496
- const value = compileExpansionValue(expansion, parent, path)
533
+ const value = compileExpansionValue(expansion, parent, path, ctx)
497
534
  parts.push("?", `json(${value.sql})`)
498
535
  args.push(expansion.linkId, ...value.args)
499
536
  })
@@ -509,7 +546,8 @@ function compileExpansionsObject(
509
546
  function compileExpansionValue(
510
547
  expansion: ObjectExpansion,
511
548
  parent: ExpansionParent,
512
- path: string
549
+ path: string,
550
+ ctx: CompileContext
513
551
  ): CompiledPredicate {
514
552
  const edge = `edge_${path}`
515
553
  const target = `tgt_${path}`
@@ -522,7 +560,7 @@ function compileExpansionValue(
522
560
  const parentType = incoming ? `${edge}.target_type_id` : `${edge}.source_type_id`
523
561
  const parentId = incoming ? `${edge}.target_id` : `${edge}.source_id`
524
562
 
525
- const child = compileExpansionChildJson(expansion, edge, target, path)
563
+ const child = compileExpansionChildJson(expansion, edge, target, path, ctx)
526
564
  const order = compileExpansionOrder(expansion, target, neighborType, neighborId)
527
565
 
528
566
  const whereParts = [
@@ -539,8 +577,8 @@ function compileExpansionValue(
539
577
 
540
578
  const inner = `
541
579
  SELECT ${child.sql} AS elem, row_number() OVER (ORDER BY ${order.sql}) AS _ord
542
- FROM links AS ${edge}
543
- JOIN objects AS ${target}
580
+ FROM ${ctx.source.linksTable} AS ${edge}
581
+ JOIN ${ctx.source.objectsTable} AS ${target}
544
582
  ON ${target}.project_id = ${edge}.project_id
545
583
  AND ${target}.object_type_id = ${neighborType}
546
584
  AND ${target}.primary_id = ${neighborId}
@@ -574,7 +612,8 @@ function compileExpansionChildJson(
574
612
  expansion: ObjectExpansion,
575
613
  edge: string,
576
614
  target: string,
577
- path: string
615
+ path: string,
616
+ ctx: CompileContext
578
617
  ): CompiledPredicate {
579
618
  const fields = [
580
619
  `'projectId', ${target}.project_id`,
@@ -597,7 +636,8 @@ function compileExpansionChildJson(
597
636
  type: `${target}.object_type_id`,
598
637
  id: `${target}.primary_id`,
599
638
  },
600
- path
639
+ path,
640
+ ctx
601
641
  )
602
642
  fields.push(`'links', ${nested.sql}`)
603
643
  args.push(...nested.args)
@@ -645,14 +685,15 @@ function compileExpansionOrder(
645
685
  function compileSet(
646
686
  projectId: string,
647
687
  op: ObjectQuerySetOperation,
648
- inputs: readonly ObjectQuery[]
688
+ inputs: readonly ObjectQuery[],
689
+ ctx: CompileContext
649
690
  ): CompiledObjectQuery {
650
691
  if (inputs.length === 0) {
651
692
  const order = compileOrder(identityOrderFields())
652
693
  return {
653
694
  sql: `
654
695
  SELECT *
655
- FROM objects
696
+ FROM ${ctx.source.objectsTable}
656
697
  WHERE 1 = 0
657
698
  ORDER BY ${order.sql}
658
699
  `,
@@ -667,7 +708,7 @@ function compileSet(
667
708
  }
668
709
 
669
710
  const compiledInputs = inputs.map((input) =>
670
- compileObjectQueryInternal(projectId, input, exactContext)
711
+ compileObjectQueryInternal(projectId, input, exactContext(ctx))
671
712
  )
672
713
  const identities = compileSetIdentities(op, compiledInputs)
673
714
  const order = compileOrder(identityOrderFields())
@@ -675,7 +716,7 @@ function compileSet(
675
716
  const sql = `
676
717
  SELECT selected.*, selected.properties AS _cursor_properties
677
718
  FROM (${identities.sql}) AS ids
678
- JOIN objects AS selected
719
+ JOIN ${ctx.source.objectsTable} AS selected
679
720
  ON selected.project_id = ?
680
721
  AND selected.object_type_id = ids.object_type_id
681
722
  AND selected.primary_id = ids.primary_id
@@ -778,7 +819,7 @@ function compileProjectionExpression(properties: readonly string[]): CompiledPre
778
819
  sql: `
779
820
  COALESCE(
780
821
  (
781
- SELECT json_group_object(projected.key, projected.value)
822
+ SELECT json_group_object(projected.key, ${sqliteJsonEachValue("projected")})
782
823
  FROM json_each(input.properties) AS projected
783
824
  WHERE projected.key IN (${properties.map(() => "?").join(", ")})
784
825
  ),
@@ -789,6 +830,15 @@ function compileProjectionExpression(properties: readonly string[]): CompiledPre
789
830
  }
790
831
  }
791
832
 
833
+ /** json_each exposes JSON booleans as integer SQL values; restore their JSON representation. */
834
+ export function sqliteJsonEachValue(alias: string): string {
835
+ return `CASE ${alias}.type
836
+ WHEN 'true' THEN json('true')
837
+ WHEN 'false' THEN json('false')
838
+ ELSE ${alias}.value
839
+ END`
840
+ }
841
+
792
842
  function compilePredicate(predicate: ObjectQueryPredicate): CompiledPredicate {
793
843
  if (
794
844
  "scalarKind" in predicate &&