@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
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { ProviderMaterializationTransactionLifecycle } from "@sixb/core/internal
5
5
  import {
6
6
  createAgentOperationScope,
7
7
  createAuthOperationScope,
8
+ createObjectOperationScope,
8
9
  createOntologyOperationScope,
9
10
  createOperationScopedFacade,
10
11
  createStorageOperationScope,
@@ -3632,6 +3633,304 @@ CREATE INDEX idx_auth_device_authorizations_pending
3632
3633
  ON auth_device_authorizations(project_id, status, expires_at);
3633
3634
  `;
3634
3635
 
3636
+ // src/migrations/035-share-grants.sql
3637
+ var _035_share_grants_default = `CREATE TABLE share_grants (
3638
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
3639
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
3640
+ definition_id TEXT NOT NULL CHECK (length(trim(definition_id)) > 0),
3641
+ target_object_type_id TEXT NOT NULL CHECK (length(trim(target_object_type_id)) > 0),
3642
+ target_primary_id TEXT NOT NULL CHECK (length(trim(target_primary_id)) > 0),
3643
+ issued_by_type TEXT NOT NULL CHECK (issued_by_type IN ('user', 'serviceAccount')),
3644
+ issued_by_id TEXT NOT NULL CHECK (length(trim(issued_by_id)) > 0),
3645
+ authority_version INTEGER NOT NULL CHECK (authority_version > 0),
3646
+ authority_snapshot TEXT NOT NULL CHECK (
3647
+ json_valid(authority_snapshot)
3648
+ AND json_type(authority_snapshot) = 'object'
3649
+ AND json_type(authority_snapshot, '$.version') = 'integer'
3650
+ AND json_extract(authority_snapshot, '$.version') = authority_version
3651
+ AND json_type(authority_snapshot, '$.access') = 'object'
3652
+ ),
3653
+ authority_digest TEXT NOT NULL CHECK (
3654
+ length(authority_digest) = 64
3655
+ AND authority_digest = lower(authority_digest)
3656
+ AND authority_digest NOT GLOB '*[^0-9a-f]*'
3657
+ ),
3658
+ token_hash TEXT NOT NULL CHECK (
3659
+ length(token_hash) = 64
3660
+ AND token_hash = lower(token_hash)
3661
+ AND token_hash NOT GLOB '*[^0-9a-f]*'
3662
+ ),
3663
+ destination_path TEXT NOT NULL CHECK (
3664
+ substr(destination_path, 1, 1) = '/'
3665
+ AND instr(destination_path, '?') = 0
3666
+ AND instr(destination_path, '#') = 0
3667
+ AND instr(destination_path, char(92)) = 0
3668
+ ),
3669
+ created_at TEXT NOT NULL,
3670
+ expires_at TEXT NOT NULL,
3671
+ revoked_at TEXT,
3672
+ revoked_by_type TEXT CHECK (
3673
+ revoked_by_type IS NULL OR revoked_by_type IN ('user', 'serviceAccount', 'system')
3674
+ ),
3675
+ revoked_by_id TEXT CHECK (revoked_by_id IS NULL OR length(trim(revoked_by_id)) > 0),
3676
+ PRIMARY KEY (project_id, id),
3677
+ UNIQUE (project_id, token_hash),
3678
+ CHECK (expires_at > created_at),
3679
+ CHECK (revoked_at IS NULL OR revoked_at >= created_at),
3680
+ CHECK (
3681
+ (revoked_at IS NULL AND revoked_by_type IS NULL AND revoked_by_id IS NULL)
3682
+ OR
3683
+ (revoked_at IS NOT NULL AND revoked_by_type IS NOT NULL AND revoked_by_id IS NOT NULL)
3684
+ )
3685
+ );
3686
+
3687
+ CREATE INDEX idx_share_grants_target
3688
+ ON share_grants (
3689
+ project_id,
3690
+ definition_id,
3691
+ target_object_type_id,
3692
+ target_primary_id,
3693
+ created_at DESC,
3694
+ id DESC
3695
+ );
3696
+
3697
+ CREATE INDEX idx_share_grants_project_created
3698
+ ON share_grants (project_id, created_at DESC, id DESC);
3699
+
3700
+ CREATE INDEX idx_share_grants_expiry
3701
+ ON share_grants (project_id, expires_at)
3702
+ WHERE revoked_at IS NULL;
3703
+ `;
3704
+
3705
+ // src/migrations/036-share-sessions.sql
3706
+ var _036_share_sessions_default = `-- Shared sessions and their execution provenance.
3707
+ CREATE TABLE share_sessions (
3708
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
3709
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
3710
+ grant_id TEXT NOT NULL CHECK (length(trim(grant_id)) > 0),
3711
+ token_hash TEXT NOT NULL CHECK (
3712
+ length(token_hash) = 64
3713
+ AND token_hash = lower(token_hash)
3714
+ AND token_hash NOT GLOB '*[^0-9a-f]*'
3715
+ ),
3716
+ created_at TEXT NOT NULL,
3717
+ expires_at TEXT NOT NULL,
3718
+ absolute_expires_at TEXT NOT NULL,
3719
+ revoked_at TEXT,
3720
+ PRIMARY KEY (project_id, id),
3721
+ UNIQUE (project_id, id, grant_id),
3722
+ UNIQUE (project_id, token_hash),
3723
+ FOREIGN KEY (project_id, grant_id)
3724
+ REFERENCES share_grants (project_id, id),
3725
+ CHECK (expires_at > created_at),
3726
+ CHECK (absolute_expires_at > created_at),
3727
+ CHECK (expires_at <= absolute_expires_at),
3728
+ CHECK (revoked_at IS NULL OR revoked_at >= created_at)
3729
+ );
3730
+
3731
+ CREATE INDEX idx_share_sessions_grant
3732
+ ON share_sessions (project_id, grant_id, created_at DESC, id DESC);
3733
+
3734
+ CREATE INDEX idx_share_sessions_expiry
3735
+ ON share_sessions (project_id, expires_at)
3736
+ WHERE revoked_at IS NULL;
3737
+
3738
+ -- Persist shared-session provenance on request executions without fabricating a principal.
3739
+ CREATE TABLE executions_v2 (
3740
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
3741
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
3742
+ executor_kind TEXT NOT NULL CHECK (
3743
+ executor_kind IN (
3744
+ 'request', 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow',
3745
+ 'agent', 'kernel'
3746
+ )
3747
+ ),
3748
+ executor_id TEXT NOT NULL CHECK (length(trim(executor_id)) > 0),
3749
+ source_kind TEXT NOT NULL CHECK (
3750
+ source_kind IN ('http', 'webhook', 'schedule', 'event', 'datasetVersion', 'execution')
3751
+ ),
3752
+ source_id TEXT NOT NULL CHECK (length(trim(source_id)) > 0),
3753
+ requested_by_user_id TEXT,
3754
+ requested_by_service_account_id TEXT,
3755
+ correlation_id TEXT NOT NULL CHECK (length(trim(correlation_id)) > 0),
3756
+ parent_execution_id TEXT,
3757
+ authority_kind TEXT NOT NULL CHECK (
3758
+ authority_kind IN ('principal', 'trustedPrimitive', 'delegated', 'kernel', 'disabled')
3759
+ ),
3760
+ authority_user_id TEXT,
3761
+ authority_service_account_id TEXT,
3762
+ authority_session_id TEXT,
3763
+ authority_access_token_id TEXT,
3764
+ authority_primitive_kind TEXT CHECK (
3765
+ authority_primitive_kind IS NULL
3766
+ OR authority_primitive_kind IN (
3767
+ 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow'
3768
+ )
3769
+ ),
3770
+ authority_primitive_id TEXT,
3771
+ authority_kernel_operation TEXT CHECK (
3772
+ authority_kernel_operation IS NULL OR authority_kernel_operation = 'ontology.recover'
3773
+ ),
3774
+ authority_delegation_kind TEXT CHECK (
3775
+ authority_delegation_kind IS NULL OR authority_delegation_kind = 'share'
3776
+ ),
3777
+ authority_delegation_id TEXT,
3778
+ authority_delegation_session_id TEXT,
3779
+ created_at TEXT NOT NULL,
3780
+ PRIMARY KEY (project_id, id),
3781
+ FOREIGN KEY (project_id, parent_execution_id) REFERENCES executions (project_id, id),
3782
+ FOREIGN KEY (project_id, requested_by_user_id) REFERENCES auth_users (project_id, id),
3783
+ FOREIGN KEY (project_id, requested_by_service_account_id)
3784
+ REFERENCES auth_service_accounts (project_id, id),
3785
+ FOREIGN KEY (project_id, authority_user_id) REFERENCES auth_users (project_id, id),
3786
+ FOREIGN KEY (project_id, authority_service_account_id)
3787
+ REFERENCES auth_service_accounts (project_id, id),
3788
+ FOREIGN KEY (project_id, authority_session_id) REFERENCES auth_sessions (project_id, id),
3789
+ FOREIGN KEY (project_id, authority_access_token_id)
3790
+ REFERENCES auth_access_tokens (project_id, id),
3791
+ FOREIGN KEY (
3792
+ project_id, authority_delegation_session_id, authority_delegation_id
3793
+ ) REFERENCES share_sessions (project_id, id, grant_id),
3794
+ CHECK ((requested_by_user_id IS NOT NULL) + (requested_by_service_account_id IS NOT NULL) <= 1),
3795
+ CHECK (
3796
+ (source_kind = 'execution' AND parent_execution_id = source_id)
3797
+ OR (source_kind <> 'execution' AND parent_execution_id IS NULL)
3798
+ ),
3799
+ CHECK (
3800
+ (
3801
+ authority_kind = 'principal'
3802
+ AND (authority_user_id IS NOT NULL) + (authority_service_account_id IS NOT NULL) = 1
3803
+ AND (authority_session_id IS NOT NULL) + (authority_access_token_id IS NOT NULL) <= 1
3804
+ AND (authority_session_id IS NULL OR authority_user_id IS NOT NULL)
3805
+ AND authority_primitive_kind IS NULL
3806
+ AND authority_primitive_id IS NULL
3807
+ AND authority_kernel_operation IS NULL
3808
+ AND authority_delegation_kind IS NULL
3809
+ AND authority_delegation_id IS NULL
3810
+ AND authority_delegation_session_id IS NULL
3811
+ )
3812
+ OR (
3813
+ authority_kind = 'trustedPrimitive'
3814
+ AND authority_user_id IS NULL
3815
+ AND authority_service_account_id IS NULL
3816
+ AND authority_session_id IS NULL
3817
+ AND authority_access_token_id IS NULL
3818
+ AND authority_primitive_kind IS NOT NULL
3819
+ AND authority_primitive_id IS NOT NULL
3820
+ AND length(trim(authority_primitive_id)) > 0
3821
+ AND authority_kernel_operation IS NULL
3822
+ AND authority_delegation_kind IS NULL
3823
+ AND authority_delegation_id IS NULL
3824
+ AND authority_delegation_session_id IS NULL
3825
+ )
3826
+ OR (
3827
+ authority_kind = 'delegated'
3828
+ AND authority_user_id IS NULL
3829
+ AND authority_service_account_id IS NULL
3830
+ AND authority_session_id IS NULL
3831
+ AND authority_access_token_id IS NULL
3832
+ AND authority_primitive_kind IS NULL
3833
+ AND authority_primitive_id IS NULL
3834
+ AND authority_kernel_operation IS NULL
3835
+ AND authority_delegation_kind IS NOT NULL
3836
+ AND authority_delegation_kind = 'share'
3837
+ AND authority_delegation_id IS NOT NULL
3838
+ AND length(trim(authority_delegation_id)) > 0
3839
+ AND authority_delegation_session_id IS NOT NULL
3840
+ AND length(trim(authority_delegation_session_id)) > 0
3841
+ )
3842
+ OR (
3843
+ authority_kind = 'kernel'
3844
+ AND authority_user_id IS NULL
3845
+ AND authority_service_account_id IS NULL
3846
+ AND authority_session_id IS NULL
3847
+ AND authority_access_token_id IS NULL
3848
+ AND authority_primitive_kind IS NULL
3849
+ AND authority_primitive_id IS NULL
3850
+ AND authority_kernel_operation IS NOT NULL
3851
+ AND authority_delegation_kind IS NULL
3852
+ AND authority_delegation_id IS NULL
3853
+ AND authority_delegation_session_id IS NULL
3854
+ )
3855
+ OR (
3856
+ authority_kind = 'disabled'
3857
+ AND authority_user_id IS NULL
3858
+ AND authority_service_account_id IS NULL
3859
+ AND authority_session_id IS NULL
3860
+ AND authority_access_token_id IS NULL
3861
+ AND authority_primitive_kind IS NULL
3862
+ AND authority_primitive_id IS NULL
3863
+ AND authority_kernel_operation IS NULL
3864
+ AND authority_delegation_kind IS NULL
3865
+ AND authority_delegation_id IS NULL
3866
+ AND authority_delegation_session_id IS NULL
3867
+ )
3868
+ ),
3869
+ CHECK (
3870
+ (
3871
+ executor_kind = 'request'
3872
+ AND source_kind = 'http'
3873
+ AND executor_id = source_id
3874
+ AND (
3875
+ (
3876
+ authority_kind = 'principal'
3877
+ AND requested_by_user_id IS authority_user_id
3878
+ AND requested_by_service_account_id IS authority_service_account_id
3879
+ )
3880
+ OR (
3881
+ authority_kind IN ('delegated', 'disabled')
3882
+ AND requested_by_user_id IS NULL
3883
+ AND requested_by_service_account_id IS NULL
3884
+ )
3885
+ )
3886
+ )
3887
+ OR (
3888
+ executor_kind IN ('action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow')
3889
+ AND authority_kind = 'trustedPrimitive'
3890
+ AND executor_kind = authority_primitive_kind
3891
+ )
3892
+ OR (
3893
+ executor_kind = 'agent'
3894
+ AND source_kind = 'execution'
3895
+ AND (
3896
+ (
3897
+ authority_kind = 'principal'
3898
+ AND authority_service_account_id IS NOT NULL
3899
+ AND authority_session_id IS NULL
3900
+ AND authority_access_token_id IS NULL
3901
+ )
3902
+ OR (
3903
+ authority_kind = 'principal'
3904
+ AND authority_user_id IS NOT NULL
3905
+ AND (authority_session_id IS NOT NULL) + (authority_access_token_id IS NOT NULL) = 1
3906
+ )
3907
+ OR authority_kind = 'disabled'
3908
+ )
3909
+ )
3910
+ OR (executor_kind = 'kernel' AND authority_kind = 'kernel')
3911
+ )
3912
+ );
3913
+
3914
+ INSERT INTO executions_v2 (
3915
+ project_id, id, executor_kind, executor_id, source_kind, source_id,
3916
+ requested_by_user_id, requested_by_service_account_id, correlation_id, parent_execution_id,
3917
+ authority_kind, authority_user_id, authority_service_account_id, authority_session_id,
3918
+ authority_access_token_id, authority_primitive_kind, authority_primitive_id,
3919
+ authority_kernel_operation, authority_delegation_kind, authority_delegation_id,
3920
+ authority_delegation_session_id, created_at
3921
+ )
3922
+ SELECT
3923
+ project_id, id, executor_kind, executor_id, source_kind, source_id,
3924
+ requested_by_user_id, requested_by_service_account_id, correlation_id, parent_execution_id,
3925
+ authority_kind, authority_user_id, authority_service_account_id, authority_session_id,
3926
+ authority_access_token_id, authority_primitive_kind, authority_primitive_id,
3927
+ authority_kernel_operation, NULL, NULL, NULL, created_at
3928
+ FROM executions;
3929
+
3930
+ DROP TABLE executions;
3931
+ ALTER TABLE executions_v2 RENAME TO executions;
3932
+ `;
3933
+
3635
3934
  // src/migrations.ts
3636
3935
  var MIGRATIONS_TABLE_SQL = `
3637
3936
  CREATE TABLE IF NOT EXISTS sixb_migrations (
@@ -3700,7 +3999,9 @@ var sqliteStorageMigrations = defineMigrations({
3700
3999
  sqliteSql("031-subagent-runs", _031_subagent_runs_default),
3701
4000
  sqliteSql("032-conversation-run-spec", _032_conversation_run_spec_default),
3702
4001
  sqliteSql("033-retire-agent-definitions", _033_retire_agent_definitions_default),
3703
- sqliteSql("034-device-authorizations", _034_device_authorizations_default)
4002
+ sqliteSql("034-device-authorizations", _034_device_authorizations_default),
4003
+ sqliteSql("035-share-grants", _035_share_grants_default),
4004
+ sqliteSql("036-share-sessions", _036_share_sessions_default)
3704
4005
  ]
3705
4006
  });
3706
4007
  function sqliteStoragePath(basePath) {
@@ -3879,6 +4180,7 @@ function closeSqliteStoreConnection(connection) {
3879
4180
  }
3880
4181
  }
3881
4182
  var activeImmediateTransactions = new WeakSet;
4183
+ var activeDeferredReadTransactions = new WeakSet;
3882
4184
  function runImmediateTransaction(db, run) {
3883
4185
  if (activeImmediateTransactions.has(db)) {
3884
4186
  return run();
@@ -3916,6 +4218,26 @@ async function runImmediateTransactionAsync(db, run) {
3916
4218
  activeImmediateTransactions.delete(db);
3917
4219
  }
3918
4220
  }
4221
+ function runDeferredReadTransaction(db, run) {
4222
+ if (activeImmediateTransactions.has(db) || activeDeferredReadTransactions.has(db)) {
4223
+ return run();
4224
+ }
4225
+ if (db.inTransaction) {
4226
+ throw new Error("[SixbSqlite] Selected object reads cannot join an unverified SQLite transaction.");
4227
+ }
4228
+ db.run("BEGIN DEFERRED");
4229
+ activeDeferredReadTransactions.add(db);
4230
+ try {
4231
+ const result = run();
4232
+ db.run("COMMIT");
4233
+ return result;
4234
+ } catch (error) {
4235
+ rollbackQuietly2(db);
4236
+ throw error;
4237
+ } finally {
4238
+ activeDeferredReadTransactions.delete(db);
4239
+ }
4240
+ }
3919
4241
  function rollbackQuietly2(db) {
3920
4242
  try {
3921
4243
  db.run("ROLLBACK");
@@ -4602,6 +4924,9 @@ import {
4602
4924
  function isUniqueConstraintError2(error) {
4603
4925
  return error instanceof Error && error.message.includes("UNIQUE constraint failed");
4604
4926
  }
4927
+ function isForeignKeyConstraintError(error) {
4928
+ return error instanceof Error && error.message.includes("FOREIGN KEY constraint failed");
4929
+ }
4605
4930
 
4606
4931
  // src/agents/messages.ts
4607
4932
  class SqliteAgentMessageStore {
@@ -9081,15 +9406,18 @@ import { ExecutionStorageError } from "@sixb/core/storage";
9081
9406
  class SqliteExecutionStorage {
9082
9407
  db;
9083
9408
  auth;
9084
- constructor(db, auth) {
9409
+ shareSessions;
9410
+ constructor(db, auth, shareSessions) {
9085
9411
  this.db = db;
9086
9412
  this.auth = auth;
9413
+ this.shareSessions = shareSessions;
9087
9414
  }
9088
9415
  async create(input) {
9089
9416
  const record = normalizeExecutionRecord(input);
9090
9417
  await validateExecutionRecordReferences(record, {
9091
9418
  auth: this.auth,
9092
- getExecution: (params) => this.getById(params)
9419
+ getExecution: (params) => this.getById(params),
9420
+ getShareSession: (params) => this.shareSessions.getById(params)
9093
9421
  });
9094
9422
  const row = executionRecordToStorageRow(record);
9095
9423
  try {
@@ -9113,9 +9441,12 @@ class SqliteExecutionStorage {
9113
9441
  authority_primitive_kind,
9114
9442
  authority_primitive_id,
9115
9443
  authority_kernel_operation,
9444
+ authority_delegation_kind,
9445
+ authority_delegation_id,
9446
+ authority_delegation_session_id,
9116
9447
  created_at
9117
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9118
- `).run(row.projectId, row.id, row.executorKind, row.executorId, row.sourceKind, row.sourceId, row.requestedByUserId, row.requestedByServiceAccountId, row.correlationId, row.parentExecutionId, row.authorityKind, row.authorityUserId, row.authorityServiceAccountId, row.authoritySessionId, row.authorityAccessTokenId, row.authorityPrimitiveKind, row.authorityPrimitiveId, row.authorityKernelOperation, row.createdAt.toISOString());
9448
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9449
+ `).run(row.projectId, row.id, row.executorKind, row.executorId, row.sourceKind, row.sourceId, row.requestedByUserId, row.requestedByServiceAccountId, row.correlationId, row.parentExecutionId, row.authorityKind, row.authorityUserId, row.authorityServiceAccountId, row.authoritySessionId, row.authorityAccessTokenId, row.authorityPrimitiveKind, row.authorityPrimitiveId, row.authorityKernelOperation, row.authorityDelegationKind, row.authorityDelegationId, row.authorityDelegationSessionId, row.createdAt.toISOString());
9119
9450
  } catch (error) {
9120
9451
  if (isDuplicateExecutionError(error)) {
9121
9452
  throw new ExecutionStorageError("duplicate_execution", `[SixbSqlite] Execution '${record.id}' already exists in project '${record.projectId}'.`, { cause: error });
@@ -9153,6 +9484,9 @@ function toStorageRow(row) {
9153
9484
  authorityPrimitiveKind: row.authority_primitive_kind,
9154
9485
  authorityPrimitiveId: row.authority_primitive_id,
9155
9486
  authorityKernelOperation: row.authority_kernel_operation,
9487
+ authorityDelegationKind: row.authority_delegation_kind,
9488
+ authorityDelegationId: row.authority_delegation_id,
9489
+ authorityDelegationSessionId: row.authority_delegation_session_id,
9156
9490
  createdAt: new Date(row.created_at)
9157
9491
  };
9158
9492
  }
@@ -9160,56 +9494,62 @@ function isDuplicateExecutionError(error) {
9160
9494
  return error instanceof Error && error.message.includes("UNIQUE constraint failed: executions.project_id, executions.id");
9161
9495
  }
9162
9496
 
9163
- // src/object-storage.ts
9164
- import { linkBatchKey, objectBatchKey } from "@sixb/core/storage";
9165
-
9166
- // src/object-query-compiler.ts
9497
+ // src/objects/query-compiler.ts
9167
9498
  import {
9168
9499
  ObjectQueryExecutionError
9169
9500
  } from "@sixb/core";
9170
9501
  var PAGE_TOKEN_PREFIX = "keyset:";
9171
- var exactContext = { probeLimit: false };
9172
9502
  var DEFAULT_EXPANSION_FANOUT = 1000;
9173
9503
  function compileObjectQuery(projectId, query, options = {}) {
9174
- const ctx = { probeLimit: options.includeTotal === false };
9175
- return compileObjectQueryInternal(projectId, query, ctx);
9504
+ const source = options.source ?? DEFAULT_OBJECT_QUERY_SOURCE;
9505
+ const ctx = { probeLimit: options.includeTotal === false, source };
9506
+ return source.wrapQuery(compileObjectQueryInternal(projectId, query, ctx));
9507
+ }
9508
+ var DEFAULT_OBJECT_QUERY_SOURCE = {
9509
+ objectsTable: "objects",
9510
+ linksTable: "links",
9511
+ wrapStatement: (sql, args = []) => ({ sql, args: [...args] }),
9512
+ wrapQuery: (query) => query
9513
+ };
9514
+ function exactContext(ctx) {
9515
+ return ctx.probeLimit ? { ...ctx, probeLimit: false } : ctx;
9176
9516
  }
9177
9517
  function compileObjectQueryInternal(projectId, query, ctx) {
9178
9518
  switch (query.kind) {
9179
9519
  case "start":
9180
- return compileStart(projectId, query);
9520
+ return compileStart(projectId, query, ctx);
9181
9521
  case "refs":
9182
- return compileRefs(projectId, query);
9522
+ return compileRefs(projectId, query, ctx);
9183
9523
  case "filter":
9184
- return compileFilter(projectId, query.input, query.predicate);
9524
+ return compileFilter(projectId, query.input, query.predicate, ctx);
9185
9525
  case "sort":
9186
9526
  return compileSort(projectId, query.input, query.fields, ctx);
9187
9527
  case "limit":
9188
9528
  return compileLimit(projectId, query.input, query.limit, ctx);
9189
9529
  case "page":
9190
- return compilePage(projectId, query.input, query.pageSize, query.pageToken);
9530
+ return compilePage(projectId, query.input, query.pageSize, query.pageToken, ctx);
9191
9531
  case "traverse":
9192
- return compileTraversal(projectId, query.input, query.linkId, query.direction, query.sourceObjectTypeId);
9532
+ return compileTraversal(projectId, query.input, query.linkId, query.direction, query.sourceObjectTypeId, ctx);
9193
9533
  case "set":
9194
- return compileSet(projectId, query.op, query.inputs);
9534
+ return compileSet(projectId, query.op, query.inputs, ctx);
9195
9535
  case "project":
9196
9536
  return compileProject(projectId, query.input, query.properties, ctx);
9197
9537
  case "text":
9198
- return compileText(projectId, query.input, query.query, query.fields, query.fieldsByObjectType);
9538
+ return compileText(projectId, query.input, query.query, query.fields, query.fieldsByObjectType, ctx);
9199
9539
  case "expand":
9200
- return compileExpand(projectId, query.input, query.expansions);
9540
+ return compileExpand(projectId, query.input, query.expansions, ctx);
9201
9541
  case "vector":
9202
9542
  throw new Error(`[Sixb] SQLite object storage does not support query node '${query.kind}'`);
9203
9543
  }
9204
9544
  }
9205
- function compileStart(projectId, query) {
9545
+ function compileStart(projectId, query, ctx) {
9206
9546
  if (query.includeSubtypes === true) {
9207
9547
  throw new Error("[Sixb] SQLite object storage does not support start.includeSubtypes");
9208
9548
  }
9209
9549
  const order = compileOrder(identityOrderFields());
9210
9550
  const sql = `
9211
9551
  SELECT *, properties AS _cursor_properties
9212
- FROM objects
9552
+ FROM ${ctx.source.objectsTable}
9213
9553
  WHERE project_id = ? AND object_type_id = ?
9214
9554
  ORDER BY ${order.sql}
9215
9555
  `;
@@ -9217,7 +9557,7 @@ function compileStart(projectId, query) {
9217
9557
  return {
9218
9558
  sql,
9219
9559
  args,
9220
- totalSql: "SELECT COUNT(*) as total FROM objects WHERE project_id = ? AND object_type_id = ?",
9560
+ totalSql: `SELECT COUNT(*) as total FROM ${ctx.source.objectsTable} WHERE project_id = ? AND object_type_id = ?`,
9221
9561
  totalArgs: [projectId, query.objectTypeId],
9222
9562
  order,
9223
9563
  hasMore: () => false,
@@ -9227,7 +9567,7 @@ function compileStart(projectId, query) {
9227
9567
  }
9228
9568
  };
9229
9569
  }
9230
- function compileRefs(projectId, query) {
9570
+ function compileRefs(projectId, query, ctx) {
9231
9571
  if (query.refs.length === 0) {
9232
9572
  throw new Error("[Sixb] SQLite object storage requires at least one ref");
9233
9573
  }
@@ -9241,10 +9581,9 @@ function compileRefs(projectId, query) {
9241
9581
  FROM json_each(?) AS ref
9242
9582
  `;
9243
9583
  const sql = `
9244
- WITH requested AS (${requested})
9245
9584
  SELECT selected.*, selected.properties AS _cursor_properties
9246
- FROM requested
9247
- JOIN objects AS selected
9585
+ FROM (${requested}) AS requested
9586
+ JOIN ${ctx.source.objectsTable} AS selected
9248
9587
  ON selected.project_id = ?
9249
9588
  AND selected.object_type_id = requested.object_type_id
9250
9589
  AND selected.primary_id = requested.primary_id
@@ -9254,10 +9593,9 @@ function compileRefs(projectId, query) {
9254
9593
  sql,
9255
9594
  args: [refsJson, projectId, ...selectedOrder.args],
9256
9595
  totalSql: `
9257
- WITH requested AS (${requested})
9258
9596
  SELECT COUNT(*) AS total
9259
- FROM requested
9260
- JOIN objects AS selected
9597
+ FROM (${requested}) AS requested
9598
+ JOIN ${ctx.source.objectsTable} AS selected
9261
9599
  ON selected.project_id = ?
9262
9600
  AND selected.object_type_id = requested.object_type_id
9263
9601
  AND selected.primary_id = requested.primary_id
@@ -9271,12 +9609,12 @@ function compileRefs(projectId, query) {
9271
9609
  }
9272
9610
  };
9273
9611
  }
9274
- function compileFilter(projectId, inputQuery, predicateNode) {
9612
+ function compileFilter(projectId, inputQuery, predicateNode, ctx) {
9275
9613
  const predicate = compilePredicate(predicateNode);
9276
- return compileWhere(projectId, inputQuery, predicate);
9614
+ return compileWhere(projectId, inputQuery, predicate, ctx);
9277
9615
  }
9278
- function compileWhere(projectId, inputQuery, predicate) {
9279
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext);
9616
+ function compileWhere(projectId, inputQuery, predicate, ctx) {
9617
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
9280
9618
  const sql = `
9281
9619
  SELECT *
9282
9620
  FROM (${input.sql}) AS input
@@ -9300,15 +9638,15 @@ function compileWhere(projectId, inputQuery, predicate) {
9300
9638
  }
9301
9639
  };
9302
9640
  }
9303
- function compileText(projectId, inputQuery, query, fields, fieldsByObjectType) {
9641
+ function compileText(projectId, inputQuery, query, fields, fieldsByObjectType, ctx) {
9304
9642
  const predicate = fields && fields.length > 0 ? compileTextPredicate(query, fields) : compileScopedTextPredicate(query, fieldsByObjectType);
9305
9643
  if (!predicate) {
9306
9644
  throw new Error("[Sixb] SQLite object text search requires fields or resolved text defaults");
9307
9645
  }
9308
- return compileWhere(projectId, inputQuery, predicate);
9646
+ return compileWhere(projectId, inputQuery, predicate, ctx);
9309
9647
  }
9310
9648
  function compileSort(projectId, inputQuery, fields, ctx) {
9311
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext);
9649
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
9312
9650
  const probeInput = ctx.probeLimit && needsLimitProbe(inputQuery) ? compileObjectQueryInternal(projectId, inputQuery, ctx) : undefined;
9313
9651
  const order = compileOrder(sortOrderFields(fields));
9314
9652
  return {
@@ -9337,7 +9675,7 @@ function compileSort(projectId, inputQuery, fields, ctx) {
9337
9675
  };
9338
9676
  }
9339
9677
  function compileLimit(projectId, inputQuery, rawLimit, ctx) {
9340
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext);
9678
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
9341
9679
  const limit = Math.max(0, rawLimit);
9342
9680
  const rowLimit = ctx.probeLimit ? limit + 1 : limit;
9343
9681
  return {
@@ -9361,8 +9699,8 @@ function compileLimit(projectId, inputQuery, rawLimit, ctx) {
9361
9699
  }
9362
9700
  };
9363
9701
  }
9364
- function compilePage(projectId, inputQuery, rawPageSize, pageToken) {
9365
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext);
9702
+ function compilePage(projectId, inputQuery, rawPageSize, pageToken, ctx) {
9703
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
9366
9704
  const pageSize = Math.max(0, rawPageSize);
9367
9705
  const cursor = pageToken ? decodePageToken(pageToken, input.order.fields) : undefined;
9368
9706
  const cursorPredicate = cursor ? compileKeysetPredicate(input.order.fields, cursor, input.order.propertyColumn) : undefined;
@@ -9391,27 +9729,27 @@ function compilePage(projectId, inputQuery, rawPageSize, pageToken) {
9391
9729
  }
9392
9730
  };
9393
9731
  }
9394
- function compileTraversal(projectId, inputQuery, linkId, direction, sourceObjectTypeId) {
9395
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext);
9732
+ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObjectTypeId, ctx) {
9733
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
9396
9734
  const outputAlias = direction === "outgoing" ? "target_object" : "source_object";
9397
9735
  const joinSql = direction === "outgoing" ? `
9398
- JOIN links AS edge
9736
+ JOIN ${ctx.source.linksTable} AS edge
9399
9737
  ON edge.project_id = input.project_id
9400
9738
  AND edge.source_type_id = input.object_type_id
9401
9739
  AND edge.source_id = input.primary_id
9402
9740
  AND edge.link_id = ?
9403
- JOIN objects AS target_object
9741
+ JOIN ${ctx.source.objectsTable} AS target_object
9404
9742
  ON target_object.project_id = edge.project_id
9405
9743
  AND target_object.object_type_id = edge.target_type_id
9406
9744
  AND target_object.primary_id = edge.target_id
9407
9745
  ` : `
9408
- JOIN links AS edge
9746
+ JOIN ${ctx.source.linksTable} AS edge
9409
9747
  ON edge.project_id = input.project_id
9410
9748
  AND edge.target_type_id = input.object_type_id
9411
9749
  AND edge.target_id = input.primary_id
9412
9750
  AND edge.link_id = ?${sourceObjectTypeId === undefined ? "" : `
9413
9751
  AND edge.source_type_id = ?`}
9414
- JOIN objects AS source_object
9752
+ JOIN ${ctx.source.objectsTable} AS source_object
9415
9753
  ON source_object.project_id = edge.project_id
9416
9754
  AND source_object.object_type_id = edge.source_type_id
9417
9755
  AND source_object.primary_id = edge.source_id
@@ -9443,9 +9781,9 @@ function compileTraversal(projectId, inputQuery, linkId, direction, sourceObject
9443
9781
  }
9444
9782
  };
9445
9783
  }
9446
- function compileExpand(projectId, inputQuery, expansions) {
9447
- const input = compileObjectQueryInternal(projectId, inputQuery, exactContext);
9448
- const expand = compileExpansionsObject(expansions, { project: "input.project_id", type: "input.object_type_id", id: "input.primary_id" }, "");
9784
+ function compileExpand(projectId, inputQuery, expansions, ctx) {
9785
+ const input = compileObjectQueryInternal(projectId, inputQuery, exactContext(ctx));
9786
+ const expand = compileExpansionsObject(expansions, { project: "input.project_id", type: "input.object_type_id", id: "input.primary_id" }, "", ctx);
9449
9787
  const inputOrder = compileOrder(input.order.fields, "input", "input._cursor_properties");
9450
9788
  const sql = `
9451
9789
  SELECT input.*, ${expand.sql} AS _expand
@@ -9464,18 +9802,18 @@ function compileExpand(projectId, inputQuery, expansions) {
9464
9802
  nextPageToken: input.nextPageToken
9465
9803
  };
9466
9804
  }
9467
- function compileExpansionsObject(expansions, parent, pathPrefix) {
9805
+ function compileExpansionsObject(expansions, parent, pathPrefix, ctx) {
9468
9806
  const parts = [];
9469
9807
  const args = [];
9470
9808
  expansions.forEach((expansion, index) => {
9471
9809
  const path = pathPrefix === "" ? `${index}` : `${pathPrefix}_${index}`;
9472
- const value = compileExpansionValue(expansion, parent, path);
9810
+ const value = compileExpansionValue(expansion, parent, path, ctx);
9473
9811
  parts.push("?", `json(${value.sql})`);
9474
9812
  args.push(expansion.linkId, ...value.args);
9475
9813
  });
9476
9814
  return { sql: `json_object(${parts.join(", ")})`, args };
9477
9815
  }
9478
- function compileExpansionValue(expansion, parent, path) {
9816
+ function compileExpansionValue(expansion, parent, path, ctx) {
9479
9817
  const edge = `edge_${path}`;
9480
9818
  const target = `tgt_${path}`;
9481
9819
  const ranked = `ranked_${path}`;
@@ -9484,7 +9822,7 @@ function compileExpansionValue(expansion, parent, path) {
9484
9822
  const neighborId = incoming ? `${edge}.source_id` : `${edge}.target_id`;
9485
9823
  const parentType = incoming ? `${edge}.target_type_id` : `${edge}.source_type_id`;
9486
9824
  const parentId = incoming ? `${edge}.target_id` : `${edge}.source_id`;
9487
- const child = compileExpansionChildJson(expansion, edge, target, path);
9825
+ const child = compileExpansionChildJson(expansion, edge, target, path, ctx);
9488
9826
  const order = compileExpansionOrder(expansion, target, neighborType, neighborId);
9489
9827
  const whereParts = [
9490
9828
  `${edge}.project_id = ${parent.project}`,
@@ -9499,8 +9837,8 @@ function compileExpansionValue(expansion, parent, path) {
9499
9837
  }
9500
9838
  const inner = `
9501
9839
  SELECT ${child.sql} AS elem, row_number() OVER (ORDER BY ${order.sql}) AS _ord
9502
- FROM links AS ${edge}
9503
- JOIN objects AS ${target}
9840
+ FROM ${ctx.source.linksTable} AS ${edge}
9841
+ JOIN ${ctx.source.objectsTable} AS ${target}
9504
9842
  ON ${target}.project_id = ${edge}.project_id
9505
9843
  AND ${target}.object_type_id = ${neighborType}
9506
9844
  AND ${target}.primary_id = ${neighborId}
@@ -9519,7 +9857,7 @@ function compileExpansionValue(expansion, parent, path) {
9519
9857
  args: [...innerArgs, limit]
9520
9858
  };
9521
9859
  }
9522
- function compileExpansionChildJson(expansion, edge, target, path) {
9860
+ function compileExpansionChildJson(expansion, edge, target, path, ctx) {
9523
9861
  const fields = [
9524
9862
  `'projectId', ${target}.project_id`,
9525
9863
  `'objectTypeId', ${target}.object_type_id`,
@@ -9537,7 +9875,7 @@ function compileExpansionChildJson(expansion, edge, target, path) {
9537
9875
  project: `${target}.project_id`,
9538
9876
  type: `${target}.object_type_id`,
9539
9877
  id: `${target}.primary_id`
9540
- }, path);
9878
+ }, path, ctx);
9541
9879
  fields.push(`'links', ${nested.sql}`);
9542
9880
  args.push(...nested.args);
9543
9881
  }
@@ -9561,13 +9899,13 @@ function compileExpansionOrder(expansion, target, neighborType, neighborId) {
9561
9899
  clauses.push(`${neighborType} ASC`, `${neighborId} ASC`);
9562
9900
  return { sql: clauses.join(", "), args };
9563
9901
  }
9564
- function compileSet(projectId, op, inputs) {
9902
+ function compileSet(projectId, op, inputs, ctx) {
9565
9903
  if (inputs.length === 0) {
9566
9904
  const order2 = compileOrder(identityOrderFields());
9567
9905
  return {
9568
9906
  sql: `
9569
9907
  SELECT *
9570
- FROM objects
9908
+ FROM ${ctx.source.objectsTable}
9571
9909
  WHERE 1 = 0
9572
9910
  ORDER BY ${order2.sql}
9573
9911
  `,
@@ -9582,14 +9920,14 @@ function compileSet(projectId, op, inputs) {
9582
9920
  }
9583
9921
  };
9584
9922
  }
9585
- const compiledInputs = inputs.map((input) => compileObjectQueryInternal(projectId, input, exactContext));
9923
+ const compiledInputs = inputs.map((input) => compileObjectQueryInternal(projectId, input, exactContext(ctx)));
9586
9924
  const identities = compileSetIdentities(op, compiledInputs);
9587
9925
  const order = compileOrder(identityOrderFields());
9588
9926
  const selectedOrder = compileOrder(identityOrderFields(), "selected");
9589
9927
  const sql = `
9590
9928
  SELECT selected.*, selected.properties AS _cursor_properties
9591
9929
  FROM (${identities.sql}) AS ids
9592
- JOIN objects AS selected
9930
+ JOIN ${ctx.source.objectsTable} AS selected
9593
9931
  ON selected.project_id = ?
9594
9932
  AND selected.object_type_id = ids.object_type_id
9595
9933
  AND selected.primary_id = ids.primary_id
@@ -9674,7 +10012,7 @@ function compileProjectionExpression(properties) {
9674
10012
  sql: `
9675
10013
  COALESCE(
9676
10014
  (
9677
- SELECT json_group_object(projected.key, projected.value)
10015
+ SELECT json_group_object(projected.key, ${sqliteJsonEachValue("projected")})
9678
10016
  FROM json_each(input.properties) AS projected
9679
10017
  WHERE projected.key IN (${properties.map(() => "?").join(", ")})
9680
10018
  ),
@@ -9684,6 +10022,13 @@ function compileProjectionExpression(properties) {
9684
10022
  args: [...properties]
9685
10023
  };
9686
10024
  }
10025
+ function sqliteJsonEachValue(alias) {
10026
+ return `CASE ${alias}.type
10027
+ WHEN 'true' THEN json('true')
10028
+ WHEN 'false' THEN json('false')
10029
+ ELSE ${alias}.value
10030
+ END`;
10031
+ }
9687
10032
  function compilePredicate(predicate) {
9688
10033
  if ("scalarKind" in predicate && predicate.scalarKind === "decimal" && (predicate.op === "lt" || predicate.op === "lte" || predicate.op === "gt" || predicate.op === "gte")) {
9689
10034
  throw new ObjectQueryExecutionError("exact_decimal_not_supported", "SQLite object storage cannot push down exact decimal predicates", "$.predicate");
@@ -10006,112 +10351,131 @@ function isPlainObject(value) {
10006
10351
  return typeof value === "object" && value !== null && !Array.isArray(value);
10007
10352
  }
10008
10353
 
10009
- // src/object-storage.ts
10010
- var SQLITE_OBJECT_QUERY_CAPABILITIES = {
10011
- queryObjects: true,
10012
- countObjects: true,
10013
- existsObjects: true,
10014
- facetObjects: true,
10015
- nodes: {
10016
- start: true,
10017
- refs: true,
10018
- filter: true,
10019
- text: true,
10020
- sort: true,
10021
- limit: true,
10022
- page: true,
10023
- traverse: true,
10024
- set: true,
10025
- project: true,
10026
- expand: true
10027
- },
10028
- predicateOps: {
10029
- and: true,
10030
- or: true,
10031
- not: true,
10032
- eq: true,
10033
- neq: true,
10034
- lt: true,
10035
- lte: true,
10036
- gt: true,
10037
- gte: true,
10038
- in: true,
10039
- exists: true,
10040
- contains: true
10041
- },
10042
- sortKinds: {
10043
- property: true
10044
- },
10045
- traversalDirections: {
10046
- outgoing: true,
10047
- incoming: true
10048
- },
10049
- setOps: {
10050
- union: true,
10051
- intersect: true,
10052
- subtract: true
10053
- },
10054
- scalarOperations: {
10055
- string: { equality: true, ordering: true },
10056
- uuid: { equality: true, ordering: true },
10057
- boolean: { equality: true },
10058
- integer: { equality: true, ordering: true },
10059
- double: { equality: true, ordering: true },
10060
- decimal: { equality: true },
10061
- date: { equality: true, ordering: true },
10062
- timestamp: { equality: true, ordering: true }
10063
- },
10064
- limits: {
10065
- totalCount: true,
10066
- stablePageTokens: true
10067
- },
10068
- notes: [
10069
- "SQLite object query pushdown supports start/refs/filter/text/sort/limit/page/traverse/set/project/expand over JSON properties and object links.",
10070
- "expand hydrates linked objects in-database (top-N per parent via row_number() + json_group_array); core resolves each expansion's cardinality before pushdown, and a mixed/unresolved one stays on the fallback.",
10071
- "Ordered decimal predicates and sorting use the bounded core fallback because SQLite has no native exact decimal type; canonical decimal equality remains pushdown-safe.",
10072
- "Relevance sorting, vector search, and unresolved start.includeSubtypes remain planner fallback or rejection cases."
10073
- ]
10074
- };
10354
+ // src/objects/reader.ts
10355
+ import { linkBatchKey, objectBatchKey } from "@sixb/core/storage";
10075
10356
 
10076
- class SqliteObjectStorage {
10077
- connection;
10078
- db;
10079
- constructor(options = {}) {
10080
- this.connection = openSqliteStoreConnection(options);
10081
- this.db = this.connection.db;
10082
- if (this.connection.installFreshSchema) {
10083
- installFreshSqliteSchema(this.db);
10084
- }
10357
+ // src/objects/rows.ts
10358
+ function rowToObject(row) {
10359
+ return {
10360
+ projectId: row.project_id,
10361
+ objectTypeId: row.object_type_id,
10362
+ primaryId: row.primary_id,
10363
+ properties: JSON.parse(row.properties),
10364
+ createdAt: new Date(row.created_at),
10365
+ updatedAt: new Date(row.updated_at),
10366
+ version: row.version,
10367
+ lastCommitId: row.last_commit_id
10368
+ };
10369
+ }
10370
+ function queryRowToObject(row) {
10371
+ const base = rowToObject(row);
10372
+ const links = reviveExpandedLinks(parseExpandColumn(row._expand));
10373
+ return links ? { ...base, links } : base;
10374
+ }
10375
+ function rowToLink(row) {
10376
+ return {
10377
+ projectId: row.project_id,
10378
+ sourceTypeId: row.source_type_id,
10379
+ sourceId: row.source_id,
10380
+ linkId: row.link_id,
10381
+ targetTypeId: row.target_type_id,
10382
+ targetId: row.target_id,
10383
+ ...row.properties === null ? {} : { properties: JSON.parse(row.properties) },
10384
+ createdAt: new Date(row.created_at),
10385
+ updatedAt: new Date(row.updated_at),
10386
+ lastCommitId: row.last_commit_id
10387
+ };
10388
+ }
10389
+ function parseExpandColumn(value) {
10390
+ if (typeof value !== "string")
10391
+ return value ?? undefined;
10392
+ return JSON.parse(value);
10393
+ }
10394
+ function reviveExpandedLinks(value) {
10395
+ if (!isPlainObject2(value))
10396
+ return;
10397
+ const links = {};
10398
+ for (const [linkId, raw] of Object.entries(value)) {
10399
+ links[linkId] = reviveExpandedLinkValue(raw);
10085
10400
  }
10086
- queryCapabilities() {
10087
- return SQLITE_OBJECT_QUERY_CAPABILITIES;
10401
+ return links;
10402
+ }
10403
+ function reviveExpandedLinkValue(value) {
10404
+ if (value === null || value === undefined)
10405
+ return null;
10406
+ if (Array.isArray(value))
10407
+ return value.map(reviveExpandedRow);
10408
+ return reviveExpandedRow(value);
10409
+ }
10410
+ function reviveExpandedRow(value) {
10411
+ const row = isPlainObject2(value) ? value : {};
10412
+ const expanded = {
10413
+ projectId: String(row.projectId),
10414
+ objectTypeId: String(row.objectTypeId),
10415
+ primaryId: String(row.primaryId),
10416
+ properties: isPlainObject2(row.properties) ? row.properties : {},
10417
+ createdAt: new Date(row.createdAt),
10418
+ updatedAt: new Date(row.updatedAt),
10419
+ version: Number(row.version),
10420
+ lastCommitId: String(row.lastCommitId)
10421
+ };
10422
+ if (isPlainObject2(row.linkProperties) && Object.keys(row.linkProperties).length > 0) {
10423
+ expanded.linkProperties = row.linkProperties;
10088
10424
  }
10089
- async queryObjects(params) {
10425
+ const links = reviveExpandedLinks(row.links);
10426
+ if (links)
10427
+ expanded.links = links;
10428
+ return expanded;
10429
+ }
10430
+ function isPlainObject2(value) {
10431
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10432
+ }
10433
+
10434
+ // src/objects/reader.ts
10435
+ class SqliteObjectReader {
10436
+ db;
10437
+ source;
10438
+ constructor(db, source) {
10439
+ this.db = db;
10440
+ this.source = source;
10441
+ }
10442
+ queryObjects(params) {
10443
+ const source = this.source;
10090
10444
  const compiled = compileObjectQuery(params.projectId, params.query, {
10091
- includeTotal: params.includeTotal
10445
+ includeTotal: params.includeTotal,
10446
+ source
10092
10447
  });
10093
10448
  const total = params.includeTotal === false ? undefined : readTotal(this.db, compiled);
10094
10449
  const rawRows = this.db.query(compiled.sql).all(...compiled.args);
10095
10450
  const rows = compiled.trimRows(rawRows);
10096
10451
  const hasMore = total === undefined && compiled.hasMoreProbe ? compiled.hasMoreProbe.hasMore(this.db.query(compiled.hasMoreProbe.sql).all(...compiled.hasMoreProbe.args).length) : compiled.hasMore(rawRows.length, total);
10097
10452
  return {
10098
- objects: rows.map((row) => this.queryRowToObject(row)),
10453
+ objects: rows.map((row) => queryRowToObject(row)),
10099
10454
  hasMore,
10100
10455
  nextPageToken: compiled.nextPageToken(rows, rawRows.length),
10101
10456
  ...total === undefined ? {} : { total }
10102
10457
  };
10103
10458
  }
10104
- async countObjects(params) {
10459
+ countObjects(params) {
10460
+ const source = this.source;
10105
10461
  return {
10106
- count: readTotal(this.db, compileObjectQuery(params.projectId, stripOuterRowShape(params.query)))
10462
+ count: readTotal(this.db, compileObjectQuery(params.projectId, stripOuterRowShape(params.query), {
10463
+ source
10464
+ }))
10107
10465
  };
10108
10466
  }
10109
- async existsObjects(params) {
10110
- const compiled = compileObjectQuery(params.projectId, existsProbeQuery(params.query));
10467
+ existsObjects(params) {
10468
+ const source = this.source;
10469
+ const compiled = compileObjectQuery(params.projectId, existsProbeQuery(params.query), {
10470
+ source
10471
+ });
10111
10472
  return { exists: this.db.query(compiled.sql).get(...compiled.args) !== null };
10112
10473
  }
10113
- async facetObjects(params) {
10114
- const compiled = compileObjectQuery(params.projectId, stripOuterRowShape(params.query));
10474
+ facetObjects(params) {
10475
+ const source = this.source;
10476
+ const compiled = compileObjectQuery(params.projectId, stripOuterRowShape(params.query), {
10477
+ source
10478
+ });
10115
10479
  return {
10116
10480
  facets: params.facets.map((facet) => ({
10117
10481
  propertyId: facet.propertyId,
@@ -10119,14 +10483,43 @@ class SqliteObjectStorage {
10119
10483
  }))
10120
10484
  };
10121
10485
  }
10122
- async getByPrimaryId(params) {
10123
- const row = this.db.query("SELECT * FROM objects WHERE project_id = ? AND object_type_id = ? AND primary_id = ?").get(params.projectId, params.objectTypeId, params.primaryId);
10124
- return row ? this.rowToObject(row) : null;
10486
+ getByPrimaryId(params) {
10487
+ const source = this.source;
10488
+ const objectsTable = source.objectsTable;
10489
+ const statement = source.wrapStatement(`SELECT * FROM ${objectsTable} WHERE project_id = ? AND object_type_id = ? AND primary_id = ?`, [params.projectId, params.objectTypeId, params.primaryId]);
10490
+ const row = this.db.query(statement.sql).get(...statement.args);
10491
+ return row ? rowToObject(row) : null;
10125
10492
  }
10126
- async listLinks(params) {
10493
+ selectsObjectProperties(params) {
10494
+ const source = this.source;
10495
+ const result = params.items.map(() => false);
10496
+ if (params.items.length === 0)
10497
+ return result;
10498
+ const items = params.items.map((item, batchIndex) => ({ ...item, batchIndex }));
10499
+ const requestedType = "CAST(json_extract(requested.value, '$.objectTypeId') AS TEXT)";
10500
+ const requestedId = "CAST(json_extract(requested.value, '$.primaryId') AS TEXT)";
10501
+ const requestedProperty = "CAST(json_extract(requested.value, '$.propertyId') AS TEXT)";
10502
+ const selected = "objectPropertyPermissionsTable" in source;
10503
+ const storedTable = selected ? source.objectPropertyPermissionsTable : source.objectsTable;
10504
+ const propertyJoin = selected ? `AND stored.property_id = ${requestedProperty}` : "";
10505
+ const statement = source.wrapStatement(`SELECT DISTINCT
10506
+ CAST(json_extract(requested.value, '$.batchIndex') AS INTEGER) AS _batch_index
10507
+ FROM ${storedTable} AS stored
10508
+ JOIN json_each(?) AS requested
10509
+ ON stored.object_type_id = ${requestedType}
10510
+ AND stored.primary_id = ${requestedId}
10511
+ ${propertyJoin}
10512
+ WHERE stored.project_id = ?`, [JSON.stringify(items), params.projectId]);
10513
+ const rows = this.db.query(statement.sql).all(...statement.args);
10514
+ for (const row of rows)
10515
+ result[row._batch_index] = true;
10516
+ return result;
10517
+ }
10518
+ listLinks(params) {
10519
+ const source = this.source;
10127
10520
  const direction = params.direction ?? "outgoing";
10128
10521
  const directionWhere = direction === "incoming" ? "target_type_id = ? AND target_id = ?" : direction === "both" ? "((source_type_id = ? AND source_id = ?) OR (target_type_id = ? AND target_id = ?))" : "source_type_id = ? AND source_id = ?";
10129
- let query = `SELECT * FROM links WHERE project_id = ? AND ${directionWhere}`;
10522
+ let query = `SELECT * FROM ${source.linksTable} WHERE project_id = ? AND ${directionWhere}`;
10130
10523
  const args = direction === "both" ? [
10131
10524
  params.projectId,
10132
10525
  params.objectTypeId,
@@ -10138,64 +10531,104 @@ class SqliteObjectStorage {
10138
10531
  query += " AND link_id = ?";
10139
10532
  args.push(params.linkId);
10140
10533
  }
10141
- const rows = this.db.query(query).all(...args);
10142
- return rows.map((row) => this.rowToLink(row));
10534
+ const statement = source.wrapStatement(query, args);
10535
+ const rows = this.db.query(statement.sql).all(...statement.args);
10536
+ return rows.map((row) => rowToLink(row));
10143
10537
  }
10144
- async getByPrimaryIdBatch(params) {
10538
+ getByPrimaryIdBatch(params) {
10539
+ const source = this.source;
10145
10540
  const result = new Map;
10146
10541
  if (params.items.length === 0)
10147
10542
  return result;
10148
- const rows = this.db.query(`
10149
- SELECT object.*
10150
- FROM json_each(?) AS requested
10151
- JOIN objects AS object
10152
- ON object.project_id = ?
10153
- AND object.object_type_id = json_extract(requested.value, '$.objectTypeId')
10154
- AND object.primary_id = json_extract(requested.value, '$.primaryId')
10155
- `).all(JSON.stringify(params.items), params.projectId);
10156
- for (const row of rows) {
10157
- result.set(objectBatchKey(row.object_type_id, row.primary_id), this.rowToObject(row));
10543
+ const items = params.items.map((item, batchIndex) => ({ ...item, batchIndex }));
10544
+ const statement = source.wrapStatement(`
10545
+ SELECT
10546
+ object.*,
10547
+ CAST(json_extract(requested.value, '$.batchIndex') AS INTEGER) AS _batch_index
10548
+ FROM json_each(?) AS requested
10549
+ JOIN ${source.objectsTable} AS object
10550
+ ON object.project_id = ?
10551
+ AND object.object_type_id = json_extract(requested.value, '$.objectTypeId')
10552
+ AND object.primary_id = json_extract(requested.value, '$.primaryId')
10553
+ `, [JSON.stringify(items), params.projectId]);
10554
+ const rows = this.db.query(statement.sql).all(...statement.args);
10555
+ const rowsByIndex = new Map(rows.map((row) => [row._batch_index, row]));
10556
+ for (const [index, item] of params.items.entries()) {
10557
+ const row = rowsByIndex.get(index);
10558
+ if (row)
10559
+ result.set(objectBatchKey(item.objectTypeId, item.primaryId), rowToObject(row));
10158
10560
  }
10159
10561
  return result;
10160
10562
  }
10161
- async listLinksBatch(params) {
10563
+ listLinksBatch(params) {
10564
+ const source = this.source;
10162
10565
  const result = new Map;
10163
10566
  if (params.items.length === 0)
10164
10567
  return result;
10165
- const stmt = this.db.query("SELECT * FROM links WHERE project_id = ? AND source_type_id = ? AND source_id = ? AND link_id = ?");
10166
- for (const item of params.items) {
10167
- const rows = stmt.all(params.projectId, item.objectTypeId, item.objectId, item.linkId);
10168
- if (rows.length > 0) {
10169
- result.set(linkBatchKey(item.objectTypeId, item.objectId, item.linkId), rows.map((r) => this.rowToLink(r)));
10568
+ const direction = params.direction ?? "outgoing";
10569
+ const requestedType = "CAST(json_extract(requested.value, '$.objectTypeId') AS TEXT)";
10570
+ const requestedId = "CAST(json_extract(requested.value, '$.objectId') AS TEXT)";
10571
+ const requestedLink = "CAST(json_extract(requested.value, '$.linkId') AS TEXT)";
10572
+ const outgoing = `stored.source_type_id = ${requestedType} AND stored.source_id = ${requestedId}`;
10573
+ const incoming = `stored.target_type_id = ${requestedType} AND stored.target_id = ${requestedId}`;
10574
+ const directionJoin = direction === "both" ? `((${outgoing}) OR (${incoming}))` : direction === "incoming" ? incoming : outgoing;
10575
+ const items = params.items.map((item, batchIndex) => ({ ...item, batchIndex }));
10576
+ const statement = source.wrapStatement(`
10577
+ SELECT
10578
+ stored.*,
10579
+ CAST(json_extract(requested.value, '$.batchIndex') AS INTEGER) AS _batch_index
10580
+ FROM ${source.linksTable} AS stored
10581
+ JOIN json_each(?) AS requested
10582
+ ON ${directionJoin}
10583
+ AND stored.link_id = ${requestedLink}
10584
+ WHERE stored.project_id = ?
10585
+ ORDER BY _batch_index, source_type_id, source_id, link_id, target_type_id, target_id
10586
+ `, [JSON.stringify(items), params.projectId]);
10587
+ const rows = this.db.query(statement.sql).all(...statement.args);
10588
+ const rowsByIndex = params.items.map(() => new Map);
10589
+ for (const row of rows) {
10590
+ const link = rowToLink(row);
10591
+ rowsByIndex[row._batch_index]?.set(fullLinkIdentity(link), link);
10592
+ }
10593
+ for (const [index, item] of params.items.entries()) {
10594
+ const links = [...rowsByIndex[index]?.values() ?? []];
10595
+ if (links.length > 0) {
10596
+ result.set(linkBatchKey(item.objectTypeId, item.objectId, item.linkId), links);
10170
10597
  }
10171
10598
  }
10172
10599
  return result;
10173
10600
  }
10174
- async queryLinks(params) {
10175
- assertLinkQueryLimit(params.limit);
10601
+ queryLinks(params) {
10602
+ const source = this.source;
10176
10603
  if (params.objectRefs.length === 0 || params.endpointObjectTypeIds?.length === 0) {
10177
10604
  return { links: [], hasMore: false };
10178
10605
  }
10606
+ const requested = `
10607
+ SELECT DISTINCT
10608
+ json_extract(value, '$.objectTypeId') AS object_type_id,
10609
+ json_extract(value, '$.primaryId') AS object_id
10610
+ FROM json_each(?)
10611
+ `;
10612
+ const linksTable = source.linksTable;
10179
10613
  const sourceJoin = `
10180
10614
  SELECT link.*
10181
- FROM links AS link
10182
- JOIN requested
10615
+ FROM ${linksTable} AS link
10616
+ JOIN (${requested}) AS requested
10183
10617
  ON requested.object_type_id = link.source_type_id
10184
10618
  AND requested.object_id = link.source_id
10185
10619
  WHERE link.project_id = ?
10186
10620
  `;
10187
10621
  const targetJoin = `
10188
10622
  SELECT link.*
10189
- FROM links AS link
10190
- JOIN requested
10623
+ FROM ${linksTable} AS link
10624
+ JOIN (${requested}) AS requested
10191
10625
  ON requested.object_type_id = link.target_type_id
10192
10626
  AND requested.object_id = link.target_id
10193
10627
  WHERE link.project_id = ?
10194
10628
  `;
10195
10629
  const incidentSql = params.direction === "outgoing" ? sourceJoin : params.direction === "incoming" ? targetJoin : `${sourceJoin} UNION ${targetJoin}`;
10196
- const args = [JSON.stringify(params.objectRefs), params.projectId];
10197
- if (params.direction === "both")
10198
- args.push(params.projectId);
10630
+ const requestedJson = JSON.stringify(params.objectRefs);
10631
+ const args = params.direction === "both" ? [requestedJson, params.projectId, requestedJson, params.projectId] : [requestedJson, params.projectId];
10199
10632
  const predicates = [];
10200
10633
  if (params.linkId !== undefined) {
10201
10634
  predicates.push("link_id = ?");
@@ -10212,24 +10645,622 @@ class SqliteObjectStorage {
10212
10645
  }
10213
10646
  args.push(params.limit + 1);
10214
10647
  const whereSql = predicates.length > 0 ? `WHERE ${predicates.join(" AND ")}` : "";
10215
- const rows = this.db.query(`
10216
- WITH requested AS (
10217
- SELECT DISTINCT
10218
- json_extract(value, '$.objectTypeId') AS object_type_id,
10219
- json_extract(value, '$.primaryId') AS object_id
10220
- FROM json_each(?)
10221
- ), incident AS (${incidentSql})
10222
- SELECT *
10223
- FROM incident
10224
- ${whereSql}
10225
- ORDER BY source_type_id, source_id, link_id, target_type_id, target_id
10226
- LIMIT ?
10227
- `).all(...args);
10648
+ const statement = source.wrapStatement(`
10649
+ SELECT *
10650
+ FROM (${incidentSql}) AS incident
10651
+ ${whereSql}
10652
+ ORDER BY source_type_id, source_id, link_id, target_type_id, target_id
10653
+ LIMIT ?
10654
+ `, args);
10655
+ const rows = this.db.query(statement.sql).all(...statement.args);
10228
10656
  return {
10229
- links: rows.slice(0, params.limit).map((row) => this.rowToLink(row)),
10657
+ links: rows.slice(0, params.limit).map((row) => rowToLink(row)),
10230
10658
  hasMore: rows.length > params.limit
10231
10659
  };
10232
10660
  }
10661
+ list(params) {
10662
+ const source = this.source;
10663
+ let query = `SELECT * FROM ${source.objectsTable} WHERE project_id = ?`;
10664
+ const args = [params.projectId];
10665
+ if (params.objectTypeId) {
10666
+ if (typeof params.objectTypeId === "string") {
10667
+ query += " AND object_type_id = ?";
10668
+ args.push(params.objectTypeId);
10669
+ } else {
10670
+ query += ` AND object_type_id IN (${params.objectTypeId.map(() => "?").join(", ")})`;
10671
+ args.push(...params.objectTypeId);
10672
+ }
10673
+ }
10674
+ if (params.primaryIdPrefix) {
10675
+ query += " AND primary_id LIKE ?";
10676
+ args.push(`${params.primaryIdPrefix}%`);
10677
+ }
10678
+ if (params.primaryIdSuffix) {
10679
+ query += " AND primary_id LIKE ?";
10680
+ args.push(`%${params.primaryIdSuffix}`);
10681
+ }
10682
+ if (params.updatedAfter) {
10683
+ query += " AND updated_at >= ?";
10684
+ args.push(params.updatedAfter.toISOString());
10685
+ }
10686
+ if (params.updatedBefore) {
10687
+ query += " AND updated_at <= ?";
10688
+ args.push(params.updatedBefore.toISOString());
10689
+ }
10690
+ if (params.createdAfter) {
10691
+ query += " AND created_at >= ?";
10692
+ args.push(params.createdAfter.toISOString());
10693
+ }
10694
+ if (params.createdBefore) {
10695
+ query += " AND created_at <= ?";
10696
+ args.push(params.createdBefore.toISOString());
10697
+ }
10698
+ const countStatement = source.wrapStatement(`SELECT COUNT(*) as total FROM (${query}) AS filtered_objects`, args);
10699
+ const countResult = this.db.query(countStatement.sql).get(...countStatement.args);
10700
+ const total = countResult.total;
10701
+ const offset = params.offset ?? 0;
10702
+ const limit = params.limit ?? 50;
10703
+ if (limit === 0)
10704
+ return { objects: [], hasMore: offset < total, total };
10705
+ const orderBy = params.orderBy ?? "updatedAt";
10706
+ const order = params.order ?? "desc";
10707
+ const orderColumn = orderBy === "primaryId" ? "primary_id" : orderBy === "createdAt" ? "created_at" : "updated_at";
10708
+ query += ` ORDER BY ${orderColumn} ${order.toUpperCase()}`;
10709
+ query += " LIMIT ? OFFSET ?";
10710
+ args.push(limit + 1, offset);
10711
+ const rowsStatement = source.wrapStatement(query, args);
10712
+ const rows = this.db.query(rowsStatement.sql).all(...rowsStatement.args);
10713
+ const hasMore = rows.length > limit;
10714
+ const objects = rows.slice(0, limit).map((row) => rowToObject(row));
10715
+ return { objects, hasMore, total };
10716
+ }
10717
+ }
10718
+ function fullLinkIdentity(link) {
10719
+ return JSON.stringify([
10720
+ link.sourceTypeId,
10721
+ link.sourceId,
10722
+ link.linkId,
10723
+ link.targetTypeId,
10724
+ link.targetId
10725
+ ]);
10726
+ }
10727
+ function assertLinkQueryLimit(limit) {
10728
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
10729
+ throw new Error("Object link query limit must be a positive safe integer.");
10730
+ }
10731
+ }
10732
+ function readTotal(db, compiled) {
10733
+ const row = db.query(compiled.totalSql).get(...compiled.totalArgs);
10734
+ return row.total;
10735
+ }
10736
+ function readFacetBuckets(db, compiled, facet) {
10737
+ const path = sqliteJsonPath(facet.propertyId);
10738
+ const rows = db.query(`
10739
+ SELECT
10740
+ json_type(input.properties, ?) AS value_type,
10741
+ json_extract(input.properties, ?) AS value,
10742
+ COUNT(*) AS count
10743
+ FROM (${compiled.sql}) AS input
10744
+ WHERE json_type(input.properties, ?) IS NOT NULL
10745
+ GROUP BY value_type, value
10746
+ ORDER BY count DESC, CAST(value AS TEXT) ASC
10747
+ LIMIT ?
10748
+ `).all(path, path, ...compiled.args, path, facet.limit);
10749
+ return rows.map((row) => ({
10750
+ value: sqliteFacetValue(row.value_type, row.value),
10751
+ count: Number(row.count)
10752
+ }));
10753
+ }
10754
+ function sqliteJsonPath(propertyId) {
10755
+ return `$."${propertyId.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
10756
+ }
10757
+ function sqliteFacetValue(valueType, value) {
10758
+ if (valueType === "true")
10759
+ return true;
10760
+ if (valueType === "false")
10761
+ return false;
10762
+ if (valueType === "null")
10763
+ return null;
10764
+ return value;
10765
+ }
10766
+ function existsProbeQuery(query) {
10767
+ return { kind: "limit", limit: 1, input: stripOuterRowShape(query) };
10768
+ }
10769
+ function stripOuterRowShape(query) {
10770
+ switch (query.kind) {
10771
+ case "limit":
10772
+ case "page":
10773
+ case "project":
10774
+ case "sort":
10775
+ return stripOuterRowShape(query.input);
10776
+ default:
10777
+ return query;
10778
+ }
10779
+ }
10780
+
10781
+ // src/objects/selected-reader.ts
10782
+ import {
10783
+ assertObjectReaderProject,
10784
+ assertObjectReadFacetCount,
10785
+ assertObjectReadOutputWithinLimit,
10786
+ ObjectReadLimitExceededError,
10787
+ snapshotObjectReadExecutionLimits
10788
+ } from "@sixb/core/storage";
10789
+
10790
+ // src/objects/read-scope.ts
10791
+ function compileSqliteSelectedObjectReadSource(projectId, scope, maxTraversalFacts) {
10792
+ const scopeJson = JSON.stringify({
10793
+ roots: scope.roots,
10794
+ objects: scope.objects,
10795
+ steps: scope.steps
10796
+ });
10797
+ const compiled = compileSelectedReadScopeCte(scopeJson, projectId);
10798
+ const wrapStatement = (sql, args = []) => ({
10799
+ sql: `${compiled.sql}
10800
+ ${sql}`,
10801
+ args: [...compiled.args, ...args]
10802
+ });
10803
+ return Object.freeze({
10804
+ objectsTable: "_sixb_scope_objects",
10805
+ linksTable: "_sixb_scope_links",
10806
+ objectPropertyPermissionsTable: "_sixb_scope_object_permissions",
10807
+ wrapStatement,
10808
+ wrapQuery: (query) => ({
10809
+ ...query,
10810
+ ...wrapStatement(query.sql, query.args),
10811
+ totalSql: `${compiled.sql}
10812
+ ${query.totalSql}`,
10813
+ totalArgs: [...compiled.args, ...query.totalArgs],
10814
+ ...query.hasMoreProbe ? {
10815
+ hasMoreProbe: {
10816
+ ...query.hasMoreProbe,
10817
+ ...wrapStatement(query.hasMoreProbe.sql, query.hasMoreProbe.args)
10818
+ }
10819
+ } : {}
10820
+ }),
10821
+ traversalProbe: wrapStatement(`
10822
+ SELECT COUNT(*) AS total
10823
+ FROM (
10824
+ SELECT 1 AS traversal_fact
10825
+ FROM _sixb_scope_root_facts
10826
+
10827
+ UNION ALL
10828
+
10829
+ SELECT 1 AS traversal_fact
10830
+ FROM _sixb_scope_reached_links
10831
+
10832
+ LIMIT ?
10833
+ ) AS bounded_traversal_facts
10834
+ `, [BigInt(maxTraversalFacts) + 1n])
10835
+ });
10836
+ }
10837
+ function compileSelectedReadScopeCte(scopeJson, projectId) {
10838
+ return {
10839
+ sql: `
10840
+ WITH RECURSIVE
10841
+ _sixb_scope_document(scope) AS (
10842
+ SELECT json(?)
10843
+ ),
10844
+ _sixb_scope_roots(root_id, node_id, object_type_id, primary_id) AS (
10845
+ SELECT
10846
+ CAST(root.key AS INTEGER),
10847
+ CAST(json_extract(root.value, '$.nodeId') AS INTEGER),
10848
+ CAST(json_extract(root.value, '$.objectTypeId') AS TEXT),
10849
+ CAST(json_extract(root.value, '$.primaryId') AS TEXT)
10850
+ FROM _sixb_scope_document AS document,
10851
+ json_each(json_extract(document.scope, '$.roots')) AS root
10852
+ ),
10853
+ _sixb_scope_root_facts(project_id, root_id, node_id, object_type_id, primary_id) AS (
10854
+ SELECT anchor.project_id, root.root_id, root.node_id, root.object_type_id, root.primary_id
10855
+ FROM _sixb_scope_roots AS root
10856
+ JOIN objects AS anchor
10857
+ ON anchor.project_id = ?
10858
+ AND anchor.object_type_id = root.object_type_id
10859
+ AND anchor.primary_id = root.primary_id
10860
+ ),
10861
+ _sixb_scope_object_selections(node_id, object_type_id, property_ids) AS (
10862
+ SELECT
10863
+ CAST(json_extract(selection.value, '$.nodeId') AS INTEGER),
10864
+ CAST(json_extract(selection.value, '$.objectTypeId') AS TEXT),
10865
+ json_extract(selection.value, '$.propertyIds')
10866
+ FROM _sixb_scope_document AS document,
10867
+ json_each(json_extract(document.scope, '$.objects')) AS selection
10868
+ ),
10869
+ _sixb_scope_link_selections(
10870
+ step_id,
10871
+ node_id,
10872
+ parent_node_id,
10873
+ source_object_type_id,
10874
+ link_id,
10875
+ target_object_type_id,
10876
+ property_ids
10877
+ ) AS (
10878
+ SELECT
10879
+ CAST(step.key AS INTEGER),
10880
+ CAST(json_extract(step.value, '$.nodeId') AS INTEGER),
10881
+ CAST(json_extract(step.value, '$.parentNodeId') AS INTEGER),
10882
+ CAST(json_extract(step.value, '$.sourceObjectTypeId') AS TEXT),
10883
+ CAST(json_extract(step.value, '$.linkId') AS TEXT),
10884
+ CAST(json_extract(step.value, '$.targetObjectTypeId') AS TEXT),
10885
+ json_extract(step.value, '$.propertyIds')
10886
+ FROM _sixb_scope_document AS document,
10887
+ json_each(json_extract(document.scope, '$.steps')) AS step
10888
+ ),
10889
+ _sixb_scope_reachable(project_id, node_id, object_type_id, primary_id) AS (
10890
+ SELECT root.project_id, root.node_id, root.object_type_id, root.primary_id
10891
+ FROM _sixb_scope_root_facts AS root
10892
+
10893
+ UNION
10894
+
10895
+ SELECT edge.project_id, selection.node_id, edge.target_type_id, edge.target_id
10896
+ FROM _sixb_scope_reachable AS parent
10897
+ JOIN _sixb_scope_link_selections AS selection
10898
+ ON selection.parent_node_id = parent.node_id
10899
+ AND selection.source_object_type_id = parent.object_type_id
10900
+ JOIN links AS edge
10901
+ ON edge.project_id = parent.project_id
10902
+ AND edge.source_type_id = parent.object_type_id
10903
+ AND edge.source_id = parent.primary_id
10904
+ AND edge.link_id = selection.link_id
10905
+ AND edge.target_type_id = selection.target_object_type_id
10906
+ JOIN objects AS target
10907
+ ON target.project_id = edge.project_id
10908
+ AND target.object_type_id = edge.target_type_id
10909
+ AND target.primary_id = edge.target_id
10910
+ ),
10911
+ _sixb_scope_object_permissions(project_id, object_type_id, primary_id, property_id) AS (
10912
+ SELECT DISTINCT
10913
+ reachable.project_id,
10914
+ reachable.object_type_id,
10915
+ reachable.primary_id,
10916
+ CAST(selected_property.value AS TEXT)
10917
+ FROM _sixb_scope_reachable AS reachable
10918
+ JOIN _sixb_scope_object_selections AS selection
10919
+ ON selection.node_id = reachable.node_id
10920
+ AND selection.object_type_id = reachable.object_type_id
10921
+ JOIN json_each(selection.property_ids) AS selected_property
10922
+ ),
10923
+ _sixb_scope_object_ids(project_id, object_type_id, primary_id) AS (
10924
+ SELECT DISTINCT project_id, object_type_id, primary_id
10925
+ FROM _sixb_scope_reachable
10926
+ ),
10927
+ _sixb_scope_objects AS (
10928
+ SELECT
10929
+ stored.project_id,
10930
+ stored.object_type_id,
10931
+ stored.primary_id,
10932
+ COALESCE(
10933
+ (
10934
+ SELECT json_group_object(property.key, ${sqliteJsonEachValue("property")})
10935
+ FROM json_each(stored.properties) AS property
10936
+ JOIN _sixb_scope_object_permissions AS permission
10937
+ ON permission.project_id = stored.project_id
10938
+ AND permission.object_type_id = stored.object_type_id
10939
+ AND permission.primary_id = stored.primary_id
10940
+ AND permission.property_id = property.key
10941
+ ),
10942
+ json('{}')
10943
+ ) AS properties,
10944
+ stored.created_at,
10945
+ stored.updated_at,
10946
+ stored.version,
10947
+ stored.last_commit_id
10948
+ FROM objects AS stored
10949
+ JOIN _sixb_scope_object_ids AS visible
10950
+ ON visible.project_id = stored.project_id
10951
+ AND visible.object_type_id = stored.object_type_id
10952
+ AND visible.primary_id = stored.primary_id
10953
+ ),
10954
+ _sixb_scope_reached_links(
10955
+ project_id,
10956
+ step_id,
10957
+ node_id,
10958
+ parent_node_id,
10959
+ source_type_id,
10960
+ source_id,
10961
+ link_id,
10962
+ target_type_id,
10963
+ target_id,
10964
+ property_ids
10965
+ ) AS (
10966
+ SELECT
10967
+ edge.project_id,
10968
+ selection.step_id,
10969
+ selection.node_id,
10970
+ selection.parent_node_id,
10971
+ edge.source_type_id,
10972
+ edge.source_id,
10973
+ edge.link_id,
10974
+ edge.target_type_id,
10975
+ edge.target_id,
10976
+ selection.property_ids
10977
+ FROM _sixb_scope_reachable AS parent
10978
+ JOIN _sixb_scope_link_selections AS selection
10979
+ ON selection.parent_node_id = parent.node_id
10980
+ AND selection.source_object_type_id = parent.object_type_id
10981
+ JOIN links AS edge
10982
+ ON edge.project_id = parent.project_id
10983
+ AND edge.source_type_id = parent.object_type_id
10984
+ AND edge.source_id = parent.primary_id
10985
+ AND edge.link_id = selection.link_id
10986
+ AND edge.target_type_id = selection.target_object_type_id
10987
+ JOIN objects AS target
10988
+ ON target.project_id = edge.project_id
10989
+ AND target.object_type_id = edge.target_type_id
10990
+ AND target.primary_id = edge.target_id
10991
+ ),
10992
+ _sixb_scope_link_permissions(
10993
+ project_id,
10994
+ source_type_id,
10995
+ source_id,
10996
+ link_id,
10997
+ target_type_id,
10998
+ target_id,
10999
+ property_id
11000
+ ) AS (
11001
+ SELECT DISTINCT
11002
+ reached.project_id,
11003
+ reached.source_type_id,
11004
+ reached.source_id,
11005
+ reached.link_id,
11006
+ reached.target_type_id,
11007
+ reached.target_id,
11008
+ CAST(selected_property.value AS TEXT)
11009
+ FROM _sixb_scope_reached_links AS reached
11010
+ JOIN json_each(reached.property_ids) AS selected_property
11011
+ ),
11012
+ _sixb_scope_link_ids(
11013
+ project_id,
11014
+ source_type_id,
11015
+ source_id,
11016
+ link_id,
11017
+ target_type_id,
11018
+ target_id
11019
+ ) AS (
11020
+ SELECT DISTINCT
11021
+ project_id,
11022
+ source_type_id,
11023
+ source_id,
11024
+ link_id,
11025
+ target_type_id,
11026
+ target_id
11027
+ FROM _sixb_scope_reached_links
11028
+ ),
11029
+ _sixb_scope_links AS (
11030
+ SELECT
11031
+ stored.project_id,
11032
+ stored.source_type_id,
11033
+ stored.source_id,
11034
+ stored.link_id,
11035
+ stored.target_type_id,
11036
+ stored.target_id,
11037
+ CASE
11038
+ WHEN stored.properties IS NULL THEN NULL
11039
+ ELSE NULLIF(
11040
+ (
11041
+ SELECT json_group_object(property.key, ${sqliteJsonEachValue("property")})
11042
+ FROM json_each(stored.properties) AS property
11043
+ JOIN _sixb_scope_link_permissions AS permission
11044
+ ON permission.project_id = stored.project_id
11045
+ AND permission.source_type_id = stored.source_type_id
11046
+ AND permission.source_id = stored.source_id
11047
+ AND permission.link_id = stored.link_id
11048
+ AND permission.target_type_id = stored.target_type_id
11049
+ AND permission.target_id = stored.target_id
11050
+ AND permission.property_id = property.key
11051
+ ),
11052
+ json('{}')
11053
+ )
11054
+ END AS properties,
11055
+ stored.created_at,
11056
+ stored.updated_at,
11057
+ stored.last_commit_id
11058
+ FROM links AS stored
11059
+ JOIN _sixb_scope_link_ids AS visible
11060
+ ON visible.project_id = stored.project_id
11061
+ AND visible.source_type_id = stored.source_type_id
11062
+ AND visible.source_id = stored.source_id
11063
+ AND visible.link_id = stored.link_id
11064
+ AND visible.target_type_id = stored.target_type_id
11065
+ AND visible.target_id = stored.target_id
11066
+ )
11067
+ `,
11068
+ args: [scopeJson, projectId]
11069
+ };
11070
+ }
11071
+
11072
+ // src/objects/selected-reader.ts
11073
+ function createSqliteSelectedReader(db, params, queryCapabilities) {
11074
+ const projectId = params.projectId;
11075
+ const limits = snapshotObjectReadExecutionLimits(params.limits);
11076
+ const source = compileSqliteSelectedObjectReadSource(projectId, params.scope, limits.maxTraversalFacts);
11077
+ const reader = new SqliteObjectReader(db, source);
11078
+ const assertProject = (actualProjectId) => assertObjectReaderProject(projectId, actualProjectId);
11079
+ const read = (run) => runDeferredReadTransaction(db, () => {
11080
+ assertTraversalBudget(db, source, limits.maxTraversalFacts);
11081
+ const value = run();
11082
+ assertObjectReadOutputWithinLimit(value, limits);
11083
+ return value;
11084
+ });
11085
+ const readMap = (run) => runDeferredReadTransaction(db, () => {
11086
+ assertTraversalBudget(db, source, limits.maxTraversalFacts);
11087
+ const value = run();
11088
+ assertObjectReadOutputWithinLimit([...value.entries()], limits);
11089
+ return value;
11090
+ });
11091
+ return Object.freeze({
11092
+ queryCapabilities,
11093
+ queryObjects: async (input) => {
11094
+ assertProject(input.projectId);
11095
+ return read(() => reader.queryObjects(input));
11096
+ },
11097
+ countObjects: async (input) => {
11098
+ assertProject(input.projectId);
11099
+ return read(() => reader.countObjects(input));
11100
+ },
11101
+ existsObjects: async (input) => {
11102
+ assertProject(input.projectId);
11103
+ return read(() => reader.existsObjects(input));
11104
+ },
11105
+ facetObjects: async (input) => {
11106
+ assertProject(input.projectId);
11107
+ assertObjectReadFacetCount(input.facets.length);
11108
+ return read(() => reader.facetObjects(input));
11109
+ },
11110
+ getByPrimaryId: async (input) => {
11111
+ assertProject(input.projectId);
11112
+ return read(() => reader.getByPrimaryId(input));
11113
+ },
11114
+ selectsObjectProperties: async (input) => {
11115
+ assertProject(input.projectId);
11116
+ return read(() => reader.selectsObjectProperties(input));
11117
+ },
11118
+ listLinks: async (input) => {
11119
+ assertProject(input.projectId);
11120
+ return read(() => reader.listLinks(input));
11121
+ },
11122
+ getByPrimaryIdBatch: async (input) => {
11123
+ assertProject(input.projectId);
11124
+ return readMap(() => reader.getByPrimaryIdBatch(input));
11125
+ },
11126
+ listLinksBatch: async (input) => {
11127
+ assertProject(input.projectId);
11128
+ return readMap(() => reader.listLinksBatch(input));
11129
+ },
11130
+ queryLinks: async (input) => {
11131
+ assertProject(input.projectId);
11132
+ assertLinkQueryLimit(input.limit);
11133
+ return read(() => reader.queryLinks(input));
11134
+ },
11135
+ list: async (input) => {
11136
+ assertProject(input.projectId);
11137
+ return read(() => reader.list(input));
11138
+ }
11139
+ });
11140
+ }
11141
+ function assertTraversalBudget(db, source, maxTraversalFacts) {
11142
+ const row = db.query(source.traversalProbe.sql).get(...source.traversalProbe.args);
11143
+ if (BigInt(row.total) > BigInt(maxTraversalFacts)) {
11144
+ throw new ObjectReadLimitExceededError("traversalFacts", maxTraversalFacts);
11145
+ }
11146
+ }
11147
+
11148
+ // src/objects/storage.ts
11149
+ var SQLITE_OBJECT_QUERY_CAPABILITIES = {
11150
+ queryObjects: true,
11151
+ countObjects: true,
11152
+ existsObjects: true,
11153
+ facetObjects: true,
11154
+ nodes: {
11155
+ start: true,
11156
+ refs: true,
11157
+ filter: true,
11158
+ text: true,
11159
+ sort: true,
11160
+ limit: true,
11161
+ page: true,
11162
+ traverse: true,
11163
+ set: true,
11164
+ project: true,
11165
+ expand: true
11166
+ },
11167
+ predicateOps: {
11168
+ and: true,
11169
+ or: true,
11170
+ not: true,
11171
+ eq: true,
11172
+ neq: true,
11173
+ lt: true,
11174
+ lte: true,
11175
+ gt: true,
11176
+ gte: true,
11177
+ in: true,
11178
+ exists: true,
11179
+ contains: true
11180
+ },
11181
+ sortKinds: {
11182
+ property: true
11183
+ },
11184
+ traversalDirections: {
11185
+ outgoing: true,
11186
+ incoming: true
11187
+ },
11188
+ setOps: {
11189
+ union: true,
11190
+ intersect: true,
11191
+ subtract: true
11192
+ },
11193
+ scalarOperations: {
11194
+ string: { equality: true, ordering: true },
11195
+ uuid: { equality: true, ordering: true },
11196
+ boolean: { equality: true },
11197
+ integer: { equality: true, ordering: true },
11198
+ double: { equality: true, ordering: true },
11199
+ decimal: { equality: true },
11200
+ date: { equality: true, ordering: true },
11201
+ timestamp: { equality: true, ordering: true }
11202
+ },
11203
+ limits: {
11204
+ totalCount: true,
11205
+ stablePageTokens: true
11206
+ },
11207
+ notes: [
11208
+ "SQLite object query pushdown supports start/refs/filter/text/sort/limit/page/traverse/set/project/expand over JSON properties and object links.",
11209
+ "expand hydrates linked objects in-database (top-N per parent via row_number() + json_group_array); core resolves each expansion's cardinality before pushdown, and a mixed/unresolved one stays on the fallback.",
11210
+ "Ordered decimal predicates and sorting use the bounded core fallback because SQLite has no native exact decimal type; canonical decimal equality remains pushdown-safe.",
11211
+ "Relevance sorting, vector search, and unresolved start.includeSubtypes remain planner fallback or rejection cases."
11212
+ ]
11213
+ };
11214
+
11215
+ class SqliteObjectStorage {
11216
+ connection;
11217
+ db;
11218
+ reader;
11219
+ constructor(options = {}) {
11220
+ this.connection = openSqliteStoreConnection(options);
11221
+ this.db = this.connection.db;
11222
+ this.reader = new SqliteObjectReader(this.db, DEFAULT_OBJECT_QUERY_SOURCE);
11223
+ if (this.connection.installFreshSchema) {
11224
+ installFreshSqliteSchema(this.db);
11225
+ }
11226
+ }
11227
+ queryCapabilities() {
11228
+ return SQLITE_OBJECT_QUERY_CAPABILITIES;
11229
+ }
11230
+ createSelectedReadScope(params) {
11231
+ return createSqliteSelectedReader(this.db, params, () => this.queryCapabilities());
11232
+ }
11233
+ async queryObjects(params) {
11234
+ return this.reader.queryObjects(params);
11235
+ }
11236
+ async countObjects(params) {
11237
+ return this.reader.countObjects(params);
11238
+ }
11239
+ async existsObjects(params) {
11240
+ return this.reader.existsObjects(params);
11241
+ }
11242
+ async facetObjects(params) {
11243
+ return this.reader.facetObjects(params);
11244
+ }
11245
+ async getByPrimaryId(params) {
11246
+ return this.reader.getByPrimaryId(params);
11247
+ }
11248
+ async selectsObjectProperties(params) {
11249
+ return this.reader.selectsObjectProperties(params);
11250
+ }
11251
+ async listLinks(params) {
11252
+ return this.reader.listLinks(params);
11253
+ }
11254
+ async getByPrimaryIdBatch(params) {
11255
+ return this.reader.getByPrimaryIdBatch(params);
11256
+ }
11257
+ async listLinksBatch(params) {
11258
+ return this.reader.listLinksBatch(params);
11259
+ }
11260
+ async queryLinks(params) {
11261
+ assertLinkQueryLimit(params.limit);
11262
+ return this.reader.queryLinks(params);
11263
+ }
10233
11264
  async listIncidentLinksBatch(params) {
10234
11265
  if (params.items.length === 0)
10235
11266
  return [];
@@ -10254,7 +11285,7 @@ class SqliteObjectStorage {
10254
11285
  AND requested.object_id = link.target_id
10255
11286
  WHERE link.project_id = ?
10256
11287
  `).all(JSON.stringify(params.items), params.projectId, params.projectId);
10257
- return rows.map((row) => this.rowToLink(row));
11288
+ return rows.map((row) => rowToLink(row));
10258
11289
  }
10259
11290
  async listByPrimaryIdPage(params) {
10260
11291
  assertReconciliationPageLimit(params.limit);
@@ -10266,7 +11297,7 @@ class SqliteObjectStorage {
10266
11297
  LIMIT ?
10267
11298
  `).all(params.projectId, params.objectTypeId, params.afterPrimaryId ?? null, params.afterPrimaryId ?? null, params.limit + 1);
10268
11299
  const hasMore = rows.length > params.limit;
10269
- const objects = rows.slice(0, params.limit).map((row) => this.rowToObject(row));
11300
+ const objects = rows.slice(0, params.limit).map((row) => rowToObject(row));
10270
11301
  const last = objects.at(-1);
10271
11302
  return {
10272
11303
  objects,
@@ -10274,88 +11305,7 @@ class SqliteObjectStorage {
10274
11305
  };
10275
11306
  }
10276
11307
  async list(params) {
10277
- let query = "SELECT * FROM objects WHERE project_id = ?";
10278
- const args = [params.projectId];
10279
- if (params.objectTypeId) {
10280
- if (typeof params.objectTypeId === "string") {
10281
- query += " AND object_type_id = ?";
10282
- args.push(params.objectTypeId);
10283
- } else {
10284
- query += ` AND object_type_id IN (${params.objectTypeId.map(() => "?").join(", ")})`;
10285
- args.push(...params.objectTypeId);
10286
- }
10287
- }
10288
- if (params.primaryIdPrefix) {
10289
- query += " AND primary_id LIKE ?";
10290
- args.push(`${params.primaryIdPrefix}%`);
10291
- }
10292
- if (params.primaryIdSuffix) {
10293
- query += " AND primary_id LIKE ?";
10294
- args.push(`%${params.primaryIdSuffix}`);
10295
- }
10296
- if (params.updatedAfter) {
10297
- query += " AND updated_at >= ?";
10298
- args.push(params.updatedAfter.toISOString());
10299
- }
10300
- if (params.updatedBefore) {
10301
- query += " AND updated_at <= ?";
10302
- args.push(params.updatedBefore.toISOString());
10303
- }
10304
- if (params.createdAfter) {
10305
- query += " AND created_at >= ?";
10306
- args.push(params.createdAfter.toISOString());
10307
- }
10308
- if (params.createdBefore) {
10309
- query += " AND created_at <= ?";
10310
- args.push(params.createdBefore.toISOString());
10311
- }
10312
- const countResult = this.db.query(`SELECT COUNT(*) as total FROM (${query})`).get(...args);
10313
- const total = countResult.total;
10314
- const offset = params.offset ?? 0;
10315
- const limit = params.limit ?? 50;
10316
- if (limit === 0)
10317
- return { objects: [], hasMore: offset < total, total };
10318
- const orderBy = params.orderBy ?? "updatedAt";
10319
- const order = params.order ?? "desc";
10320
- const orderColumn = orderBy === "primaryId" ? "primary_id" : orderBy === "createdAt" ? "created_at" : "updated_at";
10321
- query += ` ORDER BY ${orderColumn} ${order.toUpperCase()}`;
10322
- query += " LIMIT ? OFFSET ?";
10323
- args.push(limit + 1, offset);
10324
- const rows = this.db.query(query).all(...args);
10325
- const hasMore = rows.length > limit;
10326
- const objects = rows.slice(0, limit).map((row) => this.rowToObject(row));
10327
- return { objects, hasMore, total };
10328
- }
10329
- rowToObject(row) {
10330
- return {
10331
- projectId: row.project_id,
10332
- objectTypeId: row.object_type_id,
10333
- primaryId: row.primary_id,
10334
- properties: JSON.parse(row.properties),
10335
- createdAt: new Date(row.created_at),
10336
- updatedAt: new Date(row.updated_at),
10337
- version: row.version,
10338
- lastCommitId: row.last_commit_id
10339
- };
10340
- }
10341
- queryRowToObject(row) {
10342
- const base = this.rowToObject(row);
10343
- const links = reviveExpandedLinks(parseExpandColumn(row._expand));
10344
- return links ? { ...base, links } : base;
10345
- }
10346
- rowToLink(row) {
10347
- return {
10348
- projectId: row.project_id,
10349
- sourceTypeId: row.source_type_id,
10350
- sourceId: row.source_id,
10351
- linkId: row.link_id,
10352
- targetTypeId: row.target_type_id,
10353
- targetId: row.target_id,
10354
- properties: row.properties ? JSON.parse(row.properties) : undefined,
10355
- createdAt: new Date(row.created_at),
10356
- updatedAt: new Date(row.updated_at),
10357
- lastCommitId: row.last_commit_id
10358
- };
11308
+ return this.reader.list(params);
10359
11309
  }
10360
11310
  close() {
10361
11311
  closeSqliteStoreConnection(this.connection);
@@ -10366,104 +11316,6 @@ function assertReconciliationPageLimit(limit) {
10366
11316
  throw new Error("Object reconciliation page limit must be a positive safe integer.");
10367
11317
  }
10368
11318
  }
10369
- function assertLinkQueryLimit(limit) {
10370
- if (!Number.isSafeInteger(limit) || limit <= 0) {
10371
- throw new Error("Object link query limit must be a positive safe integer.");
10372
- }
10373
- }
10374
- function readTotal(db, compiled) {
10375
- const row = db.query(compiled.totalSql).get(...compiled.totalArgs);
10376
- return row.total;
10377
- }
10378
- function readFacetBuckets(db, compiled, facet) {
10379
- const path = sqliteJsonPath(facet.propertyId);
10380
- const rows = db.query(`
10381
- SELECT
10382
- json_type(input.properties, ?) AS value_type,
10383
- json_extract(input.properties, ?) AS value,
10384
- COUNT(*) AS count
10385
- FROM (${compiled.sql}) AS input
10386
- WHERE json_type(input.properties, ?) IS NOT NULL
10387
- GROUP BY value_type, value
10388
- ORDER BY count DESC, CAST(value AS TEXT) ASC
10389
- LIMIT ?
10390
- `).all(path, path, ...compiled.args, path, facet.limit);
10391
- return rows.map((row) => ({
10392
- value: sqliteFacetValue(row.value_type, row.value),
10393
- count: Number(row.count)
10394
- }));
10395
- }
10396
- function sqliteJsonPath(propertyId) {
10397
- return `$."${propertyId.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
10398
- }
10399
- function sqliteFacetValue(valueType, value) {
10400
- if (valueType === "true")
10401
- return true;
10402
- if (valueType === "false")
10403
- return false;
10404
- if (valueType === "null")
10405
- return null;
10406
- return value;
10407
- }
10408
- function parseExpandColumn(value) {
10409
- if (typeof value !== "string")
10410
- return value ?? undefined;
10411
- return JSON.parse(value);
10412
- }
10413
- function reviveExpandedLinks(value) {
10414
- if (!isPlainObject2(value))
10415
- return;
10416
- const links = {};
10417
- for (const [linkId, raw] of Object.entries(value)) {
10418
- links[linkId] = reviveExpandedLinkValue(raw);
10419
- }
10420
- return links;
10421
- }
10422
- function reviveExpandedLinkValue(value) {
10423
- if (value === null || value === undefined)
10424
- return null;
10425
- if (Array.isArray(value))
10426
- return value.map(reviveExpandedRow);
10427
- return reviveExpandedRow(value);
10428
- }
10429
- function reviveExpandedRow(value) {
10430
- const row = isPlainObject2(value) ? value : {};
10431
- const expanded = {
10432
- projectId: String(row.projectId),
10433
- objectTypeId: String(row.objectTypeId),
10434
- primaryId: String(row.primaryId),
10435
- properties: isPlainObject2(row.properties) ? row.properties : {},
10436
- createdAt: new Date(row.createdAt),
10437
- updatedAt: new Date(row.updatedAt),
10438
- version: Number(row.version),
10439
- lastCommitId: String(row.lastCommitId)
10440
- };
10441
- if (isPlainObject2(row.linkProperties) && Object.keys(row.linkProperties).length > 0) {
10442
- expanded.linkProperties = row.linkProperties;
10443
- }
10444
- const links = reviveExpandedLinks(row.links);
10445
- if (links)
10446
- expanded.links = links;
10447
- return expanded;
10448
- }
10449
- function isPlainObject2(value) {
10450
- return typeof value === "object" && value !== null && !Array.isArray(value);
10451
- }
10452
- function existsProbeQuery(query) {
10453
- return { kind: "limit", limit: 1, input: stripOuterRowShape(query) };
10454
- }
10455
- function stripOuterRowShape(query) {
10456
- switch (query.kind) {
10457
- case "limit":
10458
- case "page":
10459
- case "project":
10460
- case "sort":
10461
- return stripOuterRowShape(query.input);
10462
- default:
10463
- return query;
10464
- }
10465
- }
10466
-
10467
11319
  // src/ontology-storage/shared.ts
10468
11320
  import { parseSixbFailure as parseSixbFailure2 } from "@sixb/core/internal/errors";
10469
11321
  import { MaterializationConflictError } from "@sixb/core/internal/materialization";
@@ -14069,6 +14921,258 @@ function rowToRuleStateRecord(row) {
14069
14921
  };
14070
14922
  }
14071
14923
 
14924
+ // src/share-grant-storage.ts
14925
+ import {
14926
+ normalizeGetShareGrantByIdInput,
14927
+ normalizeListShareGrantsInput,
14928
+ normalizeRevokeShareGrantInput,
14929
+ normalizeShareGrantCreate,
14930
+ parseShareGrantRecord
14931
+ } from "@sixb/core/internal/share-grant-storage-provider";
14932
+ import { ShareGrantStorageError } from "@sixb/core/storage";
14933
+ class SqliteShareGrantStorage {
14934
+ connection;
14935
+ db;
14936
+ constructor(options = {}) {
14937
+ this.connection = openSqliteStoreConnection(options);
14938
+ this.db = this.connection.db;
14939
+ if (this.connection.installFreshSchema)
14940
+ installFreshSqliteSchema(this.db);
14941
+ }
14942
+ async create(input) {
14943
+ const record = normalizeShareGrantCreate(input);
14944
+ try {
14945
+ this.db.query(`
14946
+ INSERT INTO share_grants (
14947
+ project_id, id, definition_id, target_object_type_id, target_primary_id,
14948
+ issued_by_type, issued_by_id, authority_version, authority_snapshot,
14949
+ authority_digest, token_hash, destination_path, created_at, expires_at
14950
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
14951
+ `).run(record.projectId, record.id, record.definitionId, record.target.objectTypeId, record.target.primaryId, record.issuedBy.type, record.issuedBy.id, record.authoritySnapshot.version, JSON.stringify(record.authoritySnapshot), record.authorityDigest, record.tokenHash, record.destinationPath, record.createdAt.toISOString(), record.expiresAt.toISOString());
14952
+ } catch (error) {
14953
+ if (isUniqueConstraintError2(error)) {
14954
+ throw new ShareGrantStorageError("duplicate", `[SixbSqlite] Share grant '${record.id}' conflicts with an existing record.`, { cause: error });
14955
+ }
14956
+ throw error;
14957
+ }
14958
+ return this.require(record.projectId, record.id, "create");
14959
+ }
14960
+ async getById(input) {
14961
+ const normalized = normalizeGetShareGrantByIdInput(input);
14962
+ return this.getRow(normalized.projectId, normalized.id);
14963
+ }
14964
+ async list(input) {
14965
+ const normalized = normalizeListShareGrantsInput(input);
14966
+ const where = ["project_id = ?"];
14967
+ const args = [normalized.projectId];
14968
+ if (normalized.definitionId !== undefined) {
14969
+ where.push("definition_id = ?");
14970
+ args.push(normalized.definitionId);
14971
+ }
14972
+ if (normalized.target !== undefined) {
14973
+ where.push("target_object_type_id = ?", "target_primary_id = ?");
14974
+ args.push(normalized.target.objectTypeId, normalized.target.primaryId);
14975
+ }
14976
+ if (!normalized.includeRevoked)
14977
+ where.push("revoked_at IS NULL");
14978
+ if (!normalized.includeExpired) {
14979
+ where.push("expires_at > ?");
14980
+ args.push(normalized.now.toISOString());
14981
+ }
14982
+ const predicate = where.join(" AND ");
14983
+ const totalRow = this.db.query(`SELECT COUNT(*) AS total FROM share_grants WHERE ${predicate}`).get(...args);
14984
+ const rows = this.db.query(`
14985
+ SELECT * FROM share_grants
14986
+ WHERE ${predicate}
14987
+ ORDER BY created_at DESC, id DESC
14988
+ LIMIT ? OFFSET ?
14989
+ `).all(...args, normalized.limit, normalized.offset);
14990
+ const grants = rows.map(rowToRecord);
14991
+ return {
14992
+ grants,
14993
+ total: totalRow.total,
14994
+ hasMore: normalized.offset + grants.length < totalRow.total
14995
+ };
14996
+ }
14997
+ async revoke(input) {
14998
+ const normalized = normalizeRevokeShareGrantInput(input);
14999
+ const current = this.getRow(normalized.projectId, normalized.id);
15000
+ if (!current || current.revokedAt)
15001
+ return current;
15002
+ const revocation = normalizeRevokeShareGrantInput(normalized, current.createdAt);
15003
+ this.db.query(`
15004
+ UPDATE share_grants
15005
+ SET revoked_at = ?, revoked_by_type = ?, revoked_by_id = ?
15006
+ WHERE project_id = ? AND id = ? AND revoked_at IS NULL
15007
+ `).run(revocation.revokedAt.toISOString(), revocation.revokedBy.type, revocation.revokedBy.id, revocation.projectId, revocation.id);
15008
+ return this.require(revocation.projectId, revocation.id, "revoke");
15009
+ }
15010
+ close() {
15011
+ closeSqliteStoreConnection(this.connection);
15012
+ }
15013
+ getRow(projectId, id) {
15014
+ const row = this.db.query("SELECT * FROM share_grants WHERE project_id = ? AND id = ?").get(projectId, id);
15015
+ return row ? rowToRecord(row) : null;
15016
+ }
15017
+ require(projectId, id, operation) {
15018
+ const record = this.getRow(projectId, id);
15019
+ if (!record) {
15020
+ throw new ShareGrantStorageError("invalid_record", `[SixbSqlite] Share grant '${id}' disappeared after ${operation}.`);
15021
+ }
15022
+ return record;
15023
+ }
15024
+ }
15025
+ function rowToRecord(row) {
15026
+ const authoritySnapshot = parseStoredJson(row.authority_snapshot, "authority snapshot");
15027
+ if (typeof row.authority_version !== "number" || !isRecord(authoritySnapshot) || authoritySnapshot.version !== row.authority_version) {
15028
+ throw invalidRecord("Share authority version does not match its snapshot.");
15029
+ }
15030
+ const hasRevokedAt = row.revoked_at !== null;
15031
+ const hasRevokedBy = row.revoked_by_type !== null || row.revoked_by_id !== null;
15032
+ if (hasRevokedAt !== hasRevokedBy) {
15033
+ throw invalidRecord("Share revocation time and actor must be stored together.");
15034
+ }
15035
+ return parseShareGrantRecord({
15036
+ projectId: row.project_id,
15037
+ id: row.id,
15038
+ definitionId: row.definition_id,
15039
+ target: {
15040
+ objectTypeId: row.target_object_type_id,
15041
+ primaryId: row.target_primary_id
15042
+ },
15043
+ issuedBy: { type: row.issued_by_type, id: row.issued_by_id },
15044
+ authoritySnapshot,
15045
+ authorityDigest: row.authority_digest,
15046
+ tokenHash: row.token_hash,
15047
+ destinationPath: row.destination_path,
15048
+ createdAt: row.created_at,
15049
+ expiresAt: row.expires_at,
15050
+ ...hasRevokedAt ? { revokedAt: row.revoked_at } : {},
15051
+ ...hasRevokedBy ? { revokedBy: { type: row.revoked_by_type, id: row.revoked_by_id } } : {}
15052
+ });
15053
+ }
15054
+ function parseStoredJson(value, field) {
15055
+ if (typeof value !== "string")
15056
+ throw invalidRecord(`Share ${field} must be stored as JSON text.`);
15057
+ try {
15058
+ return JSON.parse(value);
15059
+ } catch (cause) {
15060
+ throw new ShareGrantStorageError("invalid_record", `[SixbSqlite] Share ${field} contains invalid JSON.`, { cause });
15061
+ }
15062
+ }
15063
+ function isRecord(value) {
15064
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15065
+ }
15066
+ function invalidRecord(message) {
15067
+ return new ShareGrantStorageError("invalid_record", `[SixbSqlite] ${message}`);
15068
+ }
15069
+
15070
+ // src/share-session-storage.ts
15071
+ import {
15072
+ normalizeGetShareSessionByIdInput,
15073
+ normalizeRenewShareSessionIfValidInput,
15074
+ normalizeRevokeShareSessionInput,
15075
+ normalizeShareSessionCreate,
15076
+ parseShareSessionRecord
15077
+ } from "@sixb/core/internal/share-session-storage-provider";
15078
+ import { ShareSessionStorageError } from "@sixb/core/storage";
15079
+ class SqliteShareSessionStorage {
15080
+ connection;
15081
+ db;
15082
+ constructor(options = {}) {
15083
+ this.connection = openSqliteStoreConnection(options);
15084
+ this.db = this.connection.db;
15085
+ if (this.connection.installFreshSchema)
15086
+ installFreshSqliteSchema(this.db);
15087
+ }
15088
+ async create(input) {
15089
+ const record = normalizeShareSessionCreate(input);
15090
+ try {
15091
+ const row = this.db.query(`
15092
+ INSERT INTO share_sessions (
15093
+ project_id, id, grant_id, token_hash, created_at, expires_at,
15094
+ absolute_expires_at
15095
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
15096
+ RETURNING *
15097
+ `).get(record.projectId, record.id, record.grantId, record.tokenHash, record.createdAt.toISOString(), record.expiresAt.toISOString(), record.absoluteExpiresAt.toISOString());
15098
+ if (!row)
15099
+ throw invalidRecord2(`Share session '${record.id}' disappeared after create.`);
15100
+ return rowToRecord2(row);
15101
+ } catch (error) {
15102
+ if (isUniqueConstraintError2(error)) {
15103
+ throw new ShareSessionStorageError("duplicate", `[SixbSqlite] Share session '${record.id}' conflicts with an existing record.`, { cause: error });
15104
+ }
15105
+ if (isForeignKeyConstraintError(error)) {
15106
+ throw new ShareSessionStorageError("invalid_input", `[SixbSqlite] Share grant '${record.grantId}' does not exist in project '${record.projectId}'.`, { cause: error });
15107
+ }
15108
+ throw error;
15109
+ }
15110
+ }
15111
+ async getById(input) {
15112
+ const normalized = normalizeGetShareSessionByIdInput(input);
15113
+ return this.getRow(normalized.projectId, normalized.id);
15114
+ }
15115
+ async renewIfValid(input) {
15116
+ const normalized = normalizeRenewShareSessionIfValidInput(input);
15117
+ const requestedExpiry = normalized.expiresAt.toISOString();
15118
+ const now = normalized.now.toISOString();
15119
+ const row = this.db.query(`
15120
+ UPDATE share_sessions
15121
+ SET expires_at = CASE
15122
+ WHEN ? > absolute_expires_at THEN absolute_expires_at
15123
+ WHEN ? > expires_at THEN ?
15124
+ ELSE expires_at
15125
+ END
15126
+ WHERE project_id = ?
15127
+ AND id = ?
15128
+ AND grant_id = ?
15129
+ AND token_hash = ?
15130
+ AND revoked_at IS NULL
15131
+ AND created_at <= ?
15132
+ AND expires_at > ?
15133
+ AND absolute_expires_at > ?
15134
+ RETURNING *
15135
+ `).get(requestedExpiry, requestedExpiry, requestedExpiry, normalized.projectId, normalized.id, normalized.grantId, normalized.tokenHash, now, now, now);
15136
+ return row ? rowToRecord2(row) : null;
15137
+ }
15138
+ async revoke(input) {
15139
+ const normalized = normalizeRevokeShareSessionInput(input);
15140
+ const current = this.getRow(normalized.projectId, normalized.id);
15141
+ if (!current || current.revokedAt)
15142
+ return current;
15143
+ const revocation = normalizeRevokeShareSessionInput(normalized, current.createdAt);
15144
+ const row = this.db.query(`
15145
+ UPDATE share_sessions
15146
+ SET revoked_at = ?
15147
+ WHERE project_id = ? AND id = ? AND revoked_at IS NULL
15148
+ RETURNING *
15149
+ `).get(revocation.revokedAt.toISOString(), revocation.projectId, revocation.id);
15150
+ return row ? rowToRecord2(row) : this.getRow(revocation.projectId, revocation.id);
15151
+ }
15152
+ close() {
15153
+ closeSqliteStoreConnection(this.connection);
15154
+ }
15155
+ getRow(projectId, id) {
15156
+ const row = this.db.query("SELECT * FROM share_sessions WHERE project_id = ? AND id = ?").get(projectId, id);
15157
+ return row ? rowToRecord2(row) : null;
15158
+ }
15159
+ }
15160
+ function rowToRecord2(row) {
15161
+ return parseShareSessionRecord({
15162
+ projectId: row.project_id,
15163
+ id: row.id,
15164
+ grantId: row.grant_id,
15165
+ tokenHash: row.token_hash,
15166
+ createdAt: row.created_at,
15167
+ expiresAt: row.expires_at,
15168
+ absoluteExpiresAt: row.absolute_expires_at,
15169
+ ...row.revoked_at === null ? {} : { revokedAt: row.revoked_at }
15170
+ });
15171
+ }
15172
+ function invalidRecord2(message) {
15173
+ return new ShareSessionStorageError("invalid_record", `[SixbSqlite] ${message}`);
15174
+ }
15175
+
14072
15176
  // src/sync-run-storage.ts
14073
15177
  import { parseSixbFailure as parseSixbFailure4, serializeSixbFailure as serializeSixbFailure5 } from "@sixb/core/internal/errors";
14074
15178
  import { assertSyncRunExecution } from "@sixb/core/internal/sync-run-storage-provider";
@@ -15444,6 +16548,8 @@ class SqliteStorage {
15444
16548
  timeseries;
15445
16549
  webhookRuns;
15446
16550
  rules;
16551
+ shareGrants;
16552
+ shareSessions;
15447
16553
  connectorConnections;
15448
16554
  migrators;
15449
16555
  connection;
@@ -15475,7 +16581,7 @@ class SqliteStorage {
15475
16581
  }, assertRootOperationAvailable) : scope;
15476
16582
  const readObjects = path ? new SqliteObjectStorage({ connection: this.readConnection }) : stores.objects;
15477
16583
  const readTimeseries = path ? new SqliteTimeseriesStorage({ connection: this.readConnection }) : stores.timeseries;
15478
- this.objects = createOperationScopedFacade(readObjects, readScope);
16584
+ this.objects = createObjectOperationScope(readObjects, readScope);
15479
16585
  this.ontology = createOntologyOperationScope(stores.ontology, ontologyScope);
15480
16586
  this.auth = createAuthOperationScope(stores.auth, scope);
15481
16587
  this.executions = createOperationScopedFacade(stores.executions, scope);
@@ -15492,6 +16598,8 @@ class SqliteStorage {
15492
16598
  this.workflowInterventions = createOperationScopedFacade(stores.workflowInterventions, scope);
15493
16599
  this.webhookRuns = createOperationScopedFacade(stores.webhookRuns, scope);
15494
16600
  this.rules = createOperationScopedFacade(stores.rules, scope);
16601
+ this.shareGrants = createOperationScopedFacade(stores.shareGrants, scope);
16602
+ this.shareSessions = createOperationScopedFacade(stores.shareSessions, scope);
15495
16603
  this.connectorConnections = createOperationScopedFacade(stores.connectorConnections, scope);
15496
16604
  this.migrators = options.path ? createSqliteStorageMigrators(options.path) : [];
15497
16605
  registerSqliteStorageTestingAdapter(this, (durationMs) => stores.connectorConnections.advanceTimeForTesting(durationMs));
@@ -15590,7 +16698,9 @@ class SqliteStorage {
15590
16698
  }
15591
16699
  function createSqliteStores(connection, options) {
15592
16700
  const auth = new SqliteAuthStorage({ connection });
15593
- const executions = new SqliteExecutionStorage(connection.db, auth);
16701
+ const shareGrants = new SqliteShareGrantStorage({ connection });
16702
+ const shareSessions = new SqliteShareSessionStorage({ connection });
16703
+ const executions = new SqliteExecutionStorage(connection.db, auth, shareSessions);
15594
16704
  return {
15595
16705
  objects: new SqliteObjectStorage({ connection }),
15596
16706
  ontology: new SqliteOntologyStorage({
@@ -15613,6 +16723,8 @@ function createSqliteStores(connection, options) {
15613
16723
  workflowInterventions: new SqliteWorkflowInterventionStorage({ connection }),
15614
16724
  webhookRuns: new SqliteWebhookRunStorage({ connection, executions }),
15615
16725
  rules: new SqliteRulesStorage({ connection }),
16726
+ shareGrants,
16727
+ shareSessions,
15616
16728
  connectorConnections: new SqliteConnectorConnectionStorage(connection)
15617
16729
  };
15618
16730
  }
@@ -15621,4 +16733,4 @@ export {
15621
16733
  SqliteStorage
15622
16734
  };
15623
16735
 
15624
- //# debugId=CC3E88BEF909A1E364756E2164756E21
16736
+ //# debugId=5ACA9B909AAC301E64756E2164756E21