@sixb/sqlite 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-storage/access-tokens.d.ts +0 -1
- package/dist/auth-storage/access-tokens.d.ts.map +1 -1
- package/dist/auth-storage/auth-storage.d.ts +2 -1
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/device-authorizations.d.ts.map +1 -1
- package/dist/auth-storage/rows.d.ts +7 -4
- package/dist/auth-storage/rows.d.ts.map +1 -1
- package/dist/auth-storage/sessions.d.ts +8 -1
- package/dist/auth-storage/sessions.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/file-upload-session-storage.d.ts +6 -0
- package/dist/file-upload-session-storage.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +441 -123
- package/dist/index.js.map +18 -17
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +0 -2
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +2 -1
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/workflow-intervention-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/auth-storage/access-tokens.ts +11 -18
- package/src/auth-storage/auth-storage.ts +49 -86
- package/src/auth-storage/device-authorizations.ts +3 -5
- package/src/auth-storage/rows.ts +18 -8
- package/src/auth-storage/sessions.ts +89 -4
- package/src/auth-storage/shared.ts +9 -3
- package/src/file-upload-session-storage.ts +136 -0
- package/src/index.ts +5 -0
- package/src/migrations/046-workflow-intervention-principals.sql +15 -0
- package/src/migrations/047-ontology-commit-attribution.sql +67 -0
- package/src/migrations/048-file-upload-sessions.sql +35 -0
- package/src/migrations/049-native-sessions.sql +12 -0
- package/src/migrations/050-directory-group-memberships.sql +21 -0
- package/src/migrations.ts +16 -0
- package/src/objects/query-compiler.ts +59 -4
- package/src/objects/reader.ts +4 -0
- package/src/objects/storage.ts +1 -0
- package/src/ontology-storage/materializations.ts +16 -59
- package/src/ontology-storage/shared.ts +7 -3
- package/src/workflow-intervention-storage.ts +9 -9
package/dist/index.js
CHANGED
|
@@ -4230,6 +4230,133 @@ var _045_agent_thread_sandbox_state_default = `ALTER TABLE agent_threads ADD COL
|
|
|
4230
4230
|
CHECK (sandbox_state IS NULL OR (json_valid(sandbox_state) AND json_type(sandbox_state) = 'object'));
|
|
4231
4231
|
`;
|
|
4232
4232
|
|
|
4233
|
+
// src/migrations/046-workflow-intervention-principals.sql
|
|
4234
|
+
var _046_workflow_intervention_principals_default = `-- Intervention actors were stored as { principalType, principalId }. Rewrite them to the
|
|
4235
|
+
-- canonical Principal shape { type, id } used by every other stored principal.
|
|
4236
|
+
UPDATE workflow_interventions
|
|
4237
|
+
SET submitted_by = json_object(
|
|
4238
|
+
'type', json_extract(submitted_by, '$.principalType'),
|
|
4239
|
+
'id', json_extract(submitted_by, '$.principalId')
|
|
4240
|
+
)
|
|
4241
|
+
WHERE json_extract(submitted_by, '$.principalType') IS NOT NULL;
|
|
4242
|
+
|
|
4243
|
+
UPDATE workflow_interventions
|
|
4244
|
+
SET cancelled_by = json_object(
|
|
4245
|
+
'type', json_extract(cancelled_by, '$.principalType'),
|
|
4246
|
+
'id', json_extract(cancelled_by, '$.principalId')
|
|
4247
|
+
)
|
|
4248
|
+
WHERE json_extract(cancelled_by, '$.principalType') IS NOT NULL;
|
|
4249
|
+
`;
|
|
4250
|
+
|
|
4251
|
+
// src/migrations/047-ontology-commit-attribution.sql
|
|
4252
|
+
var _047_ontology_commit_attribution_default = `-- Commits and their events name who requested the write and what ran it, replacing the
|
|
4253
|
+
-- authority-only \`actor\`. Both are copied from the commit's execution, which stays canonical and
|
|
4254
|
+
-- is immutable, so the backfill is exact. SQLite cannot add a NOT NULL column without a default;
|
|
4255
|
+
-- every insert supplies \`executor\`, and the UPDATE below replaces the placeholder on every row.
|
|
4256
|
+
ALTER TABLE ontology_commits ADD COLUMN requested_by TEXT
|
|
4257
|
+
CHECK (requested_by IS NULL OR json_valid(requested_by));
|
|
4258
|
+
ALTER TABLE ontology_commits ADD COLUMN executor TEXT NOT NULL DEFAULT '{}'
|
|
4259
|
+
CHECK (json_valid(executor));
|
|
4260
|
+
|
|
4261
|
+
UPDATE ontology_commits SET
|
|
4262
|
+
requested_by = (
|
|
4263
|
+
SELECT CASE
|
|
4264
|
+
WHEN e.requested_by_user_id IS NOT NULL
|
|
4265
|
+
THEN json_object('id', e.requested_by_user_id, 'type', 'user')
|
|
4266
|
+
WHEN e.requested_by_service_account_id IS NOT NULL
|
|
4267
|
+
THEN json_object('id', e.requested_by_service_account_id, 'type', 'serviceAccount')
|
|
4268
|
+
END
|
|
4269
|
+
FROM executions e
|
|
4270
|
+
WHERE e.project_id = ontology_commits.project_id AND e.id = ontology_commits.execution_id
|
|
4271
|
+
),
|
|
4272
|
+
executor = (
|
|
4273
|
+
SELECT CASE e.executor_kind
|
|
4274
|
+
WHEN 'request' THEN json_object('requestId', e.executor_id, 'type', 'request')
|
|
4275
|
+
WHEN 'agent' THEN json_object('runId', e.executor_id, 'type', 'agent')
|
|
4276
|
+
WHEN 'kernel' THEN json_object(
|
|
4277
|
+
'operation',
|
|
4278
|
+
CASE e.authority_kernel_operation
|
|
4279
|
+
WHEN 'ontology.recover'
|
|
4280
|
+
THEN json_object('recoveryId', e.executor_id, 'type', 'ontology.recover')
|
|
4281
|
+
ELSE json_object('indexingId', e.executor_id, 'type', 'ontology.indexVectors')
|
|
4282
|
+
END,
|
|
4283
|
+
'type', 'kernel'
|
|
4284
|
+
)
|
|
4285
|
+
ELSE json_object(
|
|
4286
|
+
'id', e.authority_primitive_id,
|
|
4287
|
+
'kind', e.executor_kind,
|
|
4288
|
+
'runId', e.executor_id,
|
|
4289
|
+
'type', 'primitive'
|
|
4290
|
+
)
|
|
4291
|
+
END
|
|
4292
|
+
FROM executions e
|
|
4293
|
+
WHERE e.project_id = ontology_commits.project_id AND e.id = ontology_commits.execution_id
|
|
4294
|
+
);
|
|
4295
|
+
|
|
4296
|
+
-- Events not yet published would otherwise reach consumers in the retired shape.
|
|
4297
|
+
UPDATE ontology_outbox SET envelope = (
|
|
4298
|
+
SELECT json_remove(
|
|
4299
|
+
CASE
|
|
4300
|
+
WHEN c.requested_by IS NULL
|
|
4301
|
+
THEN json_set(ontology_outbox.envelope, '$.executor', json(c.executor))
|
|
4302
|
+
ELSE json_set(
|
|
4303
|
+
ontology_outbox.envelope,
|
|
4304
|
+
'$.executor', json(c.executor),
|
|
4305
|
+
'$.requestedBy', json(c.requested_by)
|
|
4306
|
+
)
|
|
4307
|
+
END,
|
|
4308
|
+
'$.actor'
|
|
4309
|
+
)
|
|
4310
|
+
FROM ontology_commits c
|
|
4311
|
+
WHERE c.project_id = ontology_outbox.project_id AND c.id = ontology_outbox.commit_id
|
|
4312
|
+
)
|
|
4313
|
+
WHERE EXISTS (
|
|
4314
|
+
SELECT 1 FROM ontology_commits c
|
|
4315
|
+
WHERE c.project_id = ontology_outbox.project_id AND c.id = ontology_outbox.commit_id
|
|
4316
|
+
);
|
|
4317
|
+
|
|
4318
|
+
ALTER TABLE ontology_commits DROP COLUMN actor;
|
|
4319
|
+
`;
|
|
4320
|
+
|
|
4321
|
+
// src/migrations/049-native-sessions.sql
|
|
4322
|
+
var _049_native_sessions_default = `-- Native clients, such as the CLI, hold their session as bearer tokens: a
|
|
4323
|
+
-- short-lived access token, whose hash is \`token_hash\`, and a rotating refresh token. Browser
|
|
4324
|
+
-- sessions leave these columns NULL; \`refresh_token_hash IS NULL\` is what marks a cookie session.
|
|
4325
|
+
ALTER TABLE auth_sessions ADD COLUMN client_name TEXT;
|
|
4326
|
+
ALTER TABLE auth_sessions ADD COLUMN access_expires_at TEXT;
|
|
4327
|
+
ALTER TABLE auth_sessions ADD COLUMN refresh_token_hash TEXT;
|
|
4328
|
+
ALTER TABLE auth_sessions ADD COLUMN previous_refresh_token_hash TEXT;
|
|
4329
|
+
ALTER TABLE auth_sessions ADD COLUMN refreshed_at TEXT;
|
|
4330
|
+
|
|
4331
|
+
-- The device flow now starts a session instead of issuing a personal access token.
|
|
4332
|
+
ALTER TABLE auth_device_authorizations DROP COLUMN token_name;
|
|
4333
|
+
ALTER TABLE auth_device_authorizations DROP COLUMN token_expires_at;
|
|
4334
|
+
`;
|
|
4335
|
+
|
|
4336
|
+
// src/migrations/050-directory-group-memberships.sql
|
|
4337
|
+
var _050_directory_group_memberships_default = `-- A membership the identity provider owns is recorded with source 'directory' and synced to the
|
|
4338
|
+
-- provider's groups on every sign-in. Service accounts never sign in, so their table is unchanged.
|
|
4339
|
+
-- SQLite cannot change a CHECK constraint in place, so the table is rebuilt.
|
|
4340
|
+
CREATE TABLE auth_group_memberships_next (
|
|
4341
|
+
project_id TEXT NOT NULL,
|
|
4342
|
+
user_id TEXT NOT NULL,
|
|
4343
|
+
group_id TEXT NOT NULL,
|
|
4344
|
+
source TEXT NOT NULL CHECK (source IN ('invitation', 'manual', 'agent', 'directory')),
|
|
4345
|
+
created_at TEXT NOT NULL,
|
|
4346
|
+
PRIMARY KEY (project_id, user_id, group_id)
|
|
4347
|
+
);
|
|
4348
|
+
|
|
4349
|
+
INSERT INTO auth_group_memberships_next (project_id, user_id, group_id, source, created_at)
|
|
4350
|
+
SELECT project_id, user_id, group_id, source, created_at
|
|
4351
|
+
FROM auth_group_memberships;
|
|
4352
|
+
|
|
4353
|
+
DROP TABLE auth_group_memberships;
|
|
4354
|
+
ALTER TABLE auth_group_memberships_next RENAME TO auth_group_memberships;
|
|
4355
|
+
|
|
4356
|
+
CREATE INDEX idx_auth_group_memberships_group
|
|
4357
|
+
ON auth_group_memberships(project_id, group_id);
|
|
4358
|
+
`;
|
|
4359
|
+
|
|
4233
4360
|
// src/migrations/037-execution-requester-groups.sql
|
|
4234
4361
|
var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids TEXT NOT NULL DEFAULT '[]';
|
|
4235
4362
|
|
|
@@ -4312,6 +4439,44 @@ CREATE INDEX idx_connector_attempts_expiry
|
|
|
4312
4439
|
ON connector_authorization_attempts (project_id, connector_id, expires_at);
|
|
4313
4440
|
`;
|
|
4314
4441
|
|
|
4442
|
+
// src/migrations/048-file-upload-sessions.sql
|
|
4443
|
+
var _048_file_upload_sessions_default = `-- Durable staged-upload sessions, so an upload survives restarts and spans API replicas.
|
|
4444
|
+
-- Timestamps are ISO-8601 UTC text and compare lexicographically.
|
|
4445
|
+
CREATE TABLE file_upload_sessions (
|
|
4446
|
+
id TEXT PRIMARY KEY CHECK (length(trim(id)) > 0),
|
|
4447
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
4448
|
+
owner_principal_type TEXT NOT NULL
|
|
4449
|
+
CHECK (owner_principal_type IN ('user', 'serviceAccount', 'system')),
|
|
4450
|
+
owner_principal_id TEXT NOT NULL CHECK (length(owner_principal_id) > 0),
|
|
4451
|
+
strategy TEXT NOT NULL CHECK (strategy IN ('server', 'direct-put', 'multipart')),
|
|
4452
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'aborted')),
|
|
4453
|
+
file_name TEXT,
|
|
4454
|
+
media_type TEXT,
|
|
4455
|
+
logical_path TEXT,
|
|
4456
|
+
expected_size_bytes INTEGER CHECK (expected_size_bytes IS NULL OR expected_size_bytes >= 0),
|
|
4457
|
+
expected_digest TEXT,
|
|
4458
|
+
provider_upload TEXT CHECK (provider_upload IS NULL OR json_type(provider_upload) = 'object'),
|
|
4459
|
+
signed_parts TEXT NOT NULL CHECK (json_type(signed_parts) = 'array'),
|
|
4460
|
+
file_ref TEXT CHECK (file_ref IS NULL OR json_type(file_ref) = 'object'),
|
|
4461
|
+
created_at TEXT NOT NULL,
|
|
4462
|
+
expires_at TEXT NOT NULL,
|
|
4463
|
+
completed_at TEXT,
|
|
4464
|
+
aborted_at TEXT,
|
|
4465
|
+
-- Derived by core (fileUploadSessionReapAt); NULL while an abandoned upload awaits its abort.
|
|
4466
|
+
reap_at TEXT,
|
|
4467
|
+
CHECK ((status = 'completed') = (completed_at IS NOT NULL)),
|
|
4468
|
+
CHECK ((status = 'aborted') = (aborted_at IS NOT NULL))
|
|
4469
|
+
);
|
|
4470
|
+
|
|
4471
|
+
CREATE INDEX idx_file_upload_sessions_reap
|
|
4472
|
+
ON file_upload_sessions (reap_at)
|
|
4473
|
+
WHERE reap_at IS NOT NULL;
|
|
4474
|
+
|
|
4475
|
+
CREATE INDEX idx_file_upload_sessions_abandoned
|
|
4476
|
+
ON file_upload_sessions (project_id, expires_at)
|
|
4477
|
+
WHERE status = 'pending' AND provider_upload IS NOT NULL;
|
|
4478
|
+
`;
|
|
4479
|
+
|
|
4315
4480
|
// src/migrations.ts
|
|
4316
4481
|
var MIGRATIONS_TABLE_SQL = `
|
|
4317
4482
|
CREATE TABLE IF NOT EXISTS sixb_migrations (
|
|
@@ -4391,7 +4556,12 @@ var sqliteStorageMigrations = defineMigrations({
|
|
|
4391
4556
|
sqliteSql("042-vector-profiles", _042_vector_profiles_default),
|
|
4392
4557
|
sqliteSql("043-vector-indexing", _043_vector_indexing_default),
|
|
4393
4558
|
sqliteSql("044-vector-batching", _044_vector_batching_default),
|
|
4394
|
-
sqliteSql("045-agent-thread-sandbox-state", _045_agent_thread_sandbox_state_default)
|
|
4559
|
+
sqliteSql("045-agent-thread-sandbox-state", _045_agent_thread_sandbox_state_default),
|
|
4560
|
+
sqliteSql("046-workflow-intervention-principals", _046_workflow_intervention_principals_default),
|
|
4561
|
+
sqliteSql("047-ontology-commit-attribution", _047_ontology_commit_attribution_default),
|
|
4562
|
+
sqliteSql("048-file-upload-sessions", _048_file_upload_sessions_default),
|
|
4563
|
+
sqliteSql("049-native-sessions", _049_native_sessions_default),
|
|
4564
|
+
sqliteSql("050-directory-group-memberships", _050_directory_group_memberships_default)
|
|
4395
4565
|
]
|
|
4396
4566
|
});
|
|
4397
4567
|
function sqliteStoragePath(basePath) {
|
|
@@ -7345,7 +7515,14 @@ function rowToSessionRecord(row) {
|
|
|
7345
7515
|
revokedAt: row.revoked_at ? new Date(row.revoked_at) : undefined,
|
|
7346
7516
|
lastSeenAt: row.last_seen_at ? new Date(row.last_seen_at) : undefined,
|
|
7347
7517
|
userAgent: row.user_agent ?? undefined,
|
|
7348
|
-
ipAddress: row.ip_address ?? undefined
|
|
7518
|
+
ipAddress: row.ip_address ?? undefined,
|
|
7519
|
+
bearer: row.refresh_token_hash !== null && row.client_name !== null && row.access_expires_at !== null ? {
|
|
7520
|
+
clientName: row.client_name,
|
|
7521
|
+
accessExpiresAt: new Date(row.access_expires_at),
|
|
7522
|
+
refreshTokenHash: row.refresh_token_hash,
|
|
7523
|
+
previousRefreshTokenHash: row.previous_refresh_token_hash ?? undefined,
|
|
7524
|
+
refreshedAt: row.refreshed_at ? new Date(row.refreshed_at) : undefined
|
|
7525
|
+
} : undefined
|
|
7349
7526
|
};
|
|
7350
7527
|
}
|
|
7351
7528
|
function rowToAccessTokenRecord(row) {
|
|
@@ -7354,8 +7531,7 @@ function rowToAccessTokenRecord(row) {
|
|
|
7354
7531
|
projectId: row.project_id,
|
|
7355
7532
|
name: row.name,
|
|
7356
7533
|
kind: row.kind,
|
|
7357
|
-
|
|
7358
|
-
subjectId: row.subject_id,
|
|
7534
|
+
subject: { type: row.subject_type, id: row.subject_id },
|
|
7359
7535
|
tokenHash: row.token_hash,
|
|
7360
7536
|
groupIds: parseOptionalStringArray(row.group_ids),
|
|
7361
7537
|
createdByPrincipal: row.created_by_principal_type && row.created_by_principal_id ? {
|
|
@@ -7436,8 +7612,6 @@ function rowToDeviceAuthorizationRecord(row) {
|
|
|
7436
7612
|
deviceCodeHash: row.device_code_hash,
|
|
7437
7613
|
userCode: row.user_code,
|
|
7438
7614
|
clientName: row.client_name,
|
|
7439
|
-
tokenName: row.token_name,
|
|
7440
|
-
tokenExpiresAt: new Date(row.token_expires_at),
|
|
7441
7615
|
status: row.status,
|
|
7442
7616
|
approvedUserId: row.approved_user_id ?? undefined,
|
|
7443
7617
|
approvedSessionId: row.approved_session_id ?? undefined,
|
|
@@ -7668,9 +7842,12 @@ function createSession(db, input) {
|
|
|
7668
7842
|
expires_at,
|
|
7669
7843
|
absolute_expires_at,
|
|
7670
7844
|
user_agent,
|
|
7671
|
-
ip_address
|
|
7672
|
-
|
|
7673
|
-
|
|
7845
|
+
ip_address,
|
|
7846
|
+
client_name,
|
|
7847
|
+
access_expires_at,
|
|
7848
|
+
refresh_token_hash
|
|
7849
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7850
|
+
`).run(projectId, id, userId, strategyId, audience, tokenHash, toIso(input.createdAt), toIso(input.expiresAt), input.absoluteExpiresAt ? toIso(input.absoluteExpiresAt) : null, input.userAgent ?? null, input.ipAddress ?? null, input.bearer ? assertNonEmpty(input.bearer.clientName, "Session client name") : null, input.bearer ? toIso(input.bearer.accessExpiresAt) : null, input.bearer ? assertNonEmpty(input.bearer.refreshTokenHash, "Refresh token hash") : null);
|
|
7674
7851
|
const session = getSessionById(db, { projectId, id });
|
|
7675
7852
|
if (!session) {
|
|
7676
7853
|
throw new AuthStorageError("missing_session", `[Sixb] Failed to load session '${id}' for project '${projectId}'.`);
|
|
@@ -7818,10 +7995,10 @@ function createAuthAccessToken(db, input) {
|
|
|
7818
7995
|
const id = assertNonEmpty(input.id, "Access token id");
|
|
7819
7996
|
const projectId = assertNonEmpty(input.projectId, "Project id");
|
|
7820
7997
|
const name = assertNonEmpty(input.name, "Access token name");
|
|
7821
|
-
const subjectId = assertNonEmpty(input.
|
|
7998
|
+
const subjectId = assertNonEmpty(input.subject.id, "Access token subject id");
|
|
7822
7999
|
const tokenHash = assertNonEmpty(input.tokenHash, "Access token hash");
|
|
7823
8000
|
assertKindMatchesSubject(input);
|
|
7824
|
-
assertSubjectExists(db, projectId, input.
|
|
8001
|
+
assertSubjectExists(db, projectId, input.subject.type, subjectId);
|
|
7825
8002
|
const groupIds = input.groupIds === undefined ? undefined : normalizeGroupIds(input.groupIds);
|
|
7826
8003
|
try {
|
|
7827
8004
|
db.query(`
|
|
@@ -7840,7 +8017,7 @@ function createAuthAccessToken(db, input) {
|
|
|
7840
8017
|
created_at,
|
|
7841
8018
|
expires_at
|
|
7842
8019
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7843
|
-
`).run(projectId, id, name, input.kind, input.
|
|
8020
|
+
`).run(projectId, id, name, input.kind, input.subject.type, subjectId, tokenHash, serializeOptionalStringArray(groupIds), input.createdByPrincipal?.type ?? null, input.createdByPrincipal?.id ?? null, input.createdBySessionId ?? null, toIso(input.createdAt), toIso(input.expiresAt));
|
|
7844
8021
|
} catch (error) {
|
|
7845
8022
|
mapUniqueConstraintError(error, "duplicate_access_token", `[Sixb] Access token '${id}' already exists for project '${projectId}'.`);
|
|
7846
8023
|
}
|
|
@@ -7871,13 +8048,9 @@ class SqliteAuthAccessTokenStore {
|
|
|
7871
8048
|
whereClauses.push("kind = ?");
|
|
7872
8049
|
args.push(input.kind);
|
|
7873
8050
|
}
|
|
7874
|
-
if (input.
|
|
7875
|
-
whereClauses.push("subject_type = ?");
|
|
7876
|
-
args.push(input.
|
|
7877
|
-
}
|
|
7878
|
-
if (input.subjectId) {
|
|
7879
|
-
whereClauses.push("subject_id = ?");
|
|
7880
|
-
args.push(input.subjectId);
|
|
8051
|
+
if (input.subject) {
|
|
8052
|
+
whereClauses.push("subject_type = ?", "subject_id = ?");
|
|
8053
|
+
args.push(input.subject.type, input.subject.id);
|
|
7881
8054
|
}
|
|
7882
8055
|
if (!input.includeRevoked) {
|
|
7883
8056
|
whereClauses.push("revoked_at IS NULL");
|
|
@@ -7961,10 +8134,10 @@ function assertSubjectExists(db, projectId, subjectType, subjectId) {
|
|
|
7961
8134
|
throw new AuthStorageError2("missing_service_account", `[Sixb] Service account '${subjectId}' not found for project '${projectId}'.`);
|
|
7962
8135
|
}
|
|
7963
8136
|
function assertKindMatchesSubject(input) {
|
|
7964
|
-
if (input.kind === "personal" && input.
|
|
8137
|
+
if (input.kind === "personal" && input.subject.type === "user" || input.kind === "serviceAccount" && input.subject.type === "serviceAccount") {
|
|
7965
8138
|
return;
|
|
7966
8139
|
}
|
|
7967
|
-
throw new AuthStorageError2("invalid_input", `[Sixb] Access token kind '${input.kind}' cannot target subject type '${input.
|
|
8140
|
+
throw new AuthStorageError2("invalid_input", `[Sixb] Access token kind '${input.kind}' cannot target subject type '${input.subject.type}'.`);
|
|
7968
8141
|
}
|
|
7969
8142
|
|
|
7970
8143
|
// src/auth-storage/device-authorizations.ts
|
|
@@ -7984,10 +8157,10 @@ class SqliteAuthDeviceAuthorizationStore {
|
|
|
7984
8157
|
try {
|
|
7985
8158
|
this.db.query(`
|
|
7986
8159
|
INSERT INTO auth_device_authorizations (
|
|
7987
|
-
project_id, id, device_code_hash, user_code, client_name,
|
|
7988
|
-
|
|
7989
|
-
) VALUES (?, ?, ?, ?, ?,
|
|
7990
|
-
`).run(assertNonEmpty(input.projectId, "Project id"), assertNonEmpty(input.id, "Device authorization id"), assertNonEmpty(input.deviceCodeHash, "Device code hash"), assertNonEmpty(input.userCode, "User code"), assertNonEmpty(input.clientName, "Client name"),
|
|
8160
|
+
project_id, id, device_code_hash, user_code, client_name, status, created_at,
|
|
8161
|
+
expires_at
|
|
8162
|
+
) VALUES (?, ?, ?, ?, ?, 'pending', ?, ?)
|
|
8163
|
+
`).run(assertNonEmpty(input.projectId, "Project id"), assertNonEmpty(input.id, "Device authorization id"), assertNonEmpty(input.deviceCodeHash, "Device code hash"), assertNonEmpty(input.userCode, "User code"), assertNonEmpty(input.clientName, "Client name"), toIso(input.createdAt), toIso(input.expiresAt));
|
|
7991
8164
|
} catch (error) {
|
|
7992
8165
|
mapUniqueConstraintError(error, "duplicate_device_authorization", `[Sixb] Device authorization '${input.id}' already exists for project '${input.projectId}'.`);
|
|
7993
8166
|
}
|
|
@@ -8653,11 +8826,48 @@ class SqliteAuthSessionStore {
|
|
|
8653
8826
|
}
|
|
8654
8827
|
async findValidByTokenHash(params) {
|
|
8655
8828
|
const row = getSessionRowById(this.db, params);
|
|
8656
|
-
if (!row || row.
|
|
8829
|
+
if (!row || row.refresh_token_hash !== null || row.audience !== params.audience || row.token_hash !== params.tokenHash || !isLiveSessionRow(row, params.now)) {
|
|
8657
8830
|
return null;
|
|
8658
8831
|
}
|
|
8659
8832
|
return rowToSessionRecord(row);
|
|
8660
8833
|
}
|
|
8834
|
+
async findValidByAccessTokenHash(params) {
|
|
8835
|
+
const row = getSessionRowById(this.db, params);
|
|
8836
|
+
if (!row || row.access_expires_at === null || row.token_hash !== params.tokenHash || new Date(row.access_expires_at) <= params.now || !isLiveSessionRow(row, params.now)) {
|
|
8837
|
+
return null;
|
|
8838
|
+
}
|
|
8839
|
+
return rowToSessionRecord(row);
|
|
8840
|
+
}
|
|
8841
|
+
async rotateRefreshToken(params) {
|
|
8842
|
+
return runImmediateTransaction(this.db, () => {
|
|
8843
|
+
const row = getSessionRowById(this.db, params);
|
|
8844
|
+
if (!row?.refresh_token_hash || !isLiveSessionRow(row, params.now)) {
|
|
8845
|
+
return { status: "invalid" };
|
|
8846
|
+
}
|
|
8847
|
+
const replacedRecently = row.previous_refresh_token_hash === params.refreshTokenHash && row.refreshed_at !== null && params.now.getTime() - new Date(row.refreshed_at).getTime() <= params.reuseGraceMs;
|
|
8848
|
+
if (row.refresh_token_hash !== params.refreshTokenHash && !replacedRecently) {
|
|
8849
|
+
if (row.previous_refresh_token_hash !== params.refreshTokenHash) {
|
|
8850
|
+
return { status: "invalid" };
|
|
8851
|
+
}
|
|
8852
|
+
this.db.query("UPDATE auth_sessions SET revoked_at = ? WHERE project_id = ? AND id = ?").run(toIso(params.now), params.projectId, params.id);
|
|
8853
|
+
return { status: "reused" };
|
|
8854
|
+
}
|
|
8855
|
+
const expiresAt = toIso(params.next.expiresAt);
|
|
8856
|
+
this.db.query(`
|
|
8857
|
+
UPDATE auth_sessions
|
|
8858
|
+
SET token_hash = ?,
|
|
8859
|
+
access_expires_at = ?,
|
|
8860
|
+
previous_refresh_token_hash = refresh_token_hash,
|
|
8861
|
+
refresh_token_hash = ?,
|
|
8862
|
+
refreshed_at = ?,
|
|
8863
|
+
last_seen_at = ?,
|
|
8864
|
+
expires_at = MAX(expires_at, MIN(?, COALESCE(absolute_expires_at, ?)))
|
|
8865
|
+
WHERE project_id = ? AND id = ?
|
|
8866
|
+
`).run(params.next.tokenHash, toIso(params.next.accessExpiresAt), params.next.refreshTokenHash, toIso(params.now), toIso(params.now), expiresAt, expiresAt, params.projectId, params.id);
|
|
8867
|
+
const session = getSessionById(this.db, params);
|
|
8868
|
+
return session ? { status: "rotated", session } : { status: "invalid" };
|
|
8869
|
+
});
|
|
8870
|
+
}
|
|
8661
8871
|
async renewIfValid(params) {
|
|
8662
8872
|
return runImmediateTransaction(this.db, () => {
|
|
8663
8873
|
const now = toIso(params.now);
|
|
@@ -8673,6 +8883,7 @@ class SqliteAuthSessionStore {
|
|
|
8673
8883
|
AND id = ?
|
|
8674
8884
|
AND audience = ?
|
|
8675
8885
|
AND token_hash = ?
|
|
8886
|
+
AND refresh_token_hash IS NULL
|
|
8676
8887
|
AND revoked_at IS NULL
|
|
8677
8888
|
AND expires_at > ?
|
|
8678
8889
|
AND (absolute_expires_at IS NULL OR absolute_expires_at > ?)
|
|
@@ -8718,6 +8929,9 @@ class SqliteAuthSessionStore {
|
|
|
8718
8929
|
});
|
|
8719
8930
|
}
|
|
8720
8931
|
}
|
|
8932
|
+
function isLiveSessionRow(row, now) {
|
|
8933
|
+
return row.revoked_at === null && new Date(row.expires_at) > now && (row.absolute_expires_at === null || new Date(row.absolute_expires_at) > now);
|
|
8934
|
+
}
|
|
8721
8935
|
|
|
8722
8936
|
// src/auth-storage/users.ts
|
|
8723
8937
|
import { AuthStorageError as AuthStorageError10 } from "@sixb/core/storage";
|
|
@@ -8888,7 +9102,7 @@ class SqliteAuthStorage {
|
|
|
8888
9102
|
throw new AuthStorageError11("missing_device_authorization", `[Sixb] Device authorization '${input.id}' not found for project '${input.projectId}'.`);
|
|
8889
9103
|
}
|
|
8890
9104
|
assertCompletableDeviceAuthorization(authorization, input);
|
|
8891
|
-
const
|
|
9105
|
+
const session = createSession(this.db, input.session);
|
|
8892
9106
|
this.db.query(`
|
|
8893
9107
|
UPDATE auth_device_authorizations
|
|
8894
9108
|
SET status = 'consumed', consumed_at = ?
|
|
@@ -8900,7 +9114,7 @@ class SqliteAuthStorage {
|
|
|
8900
9114
|
status: "consumed",
|
|
8901
9115
|
consumedAt: new Date(input.completedAt)
|
|
8902
9116
|
},
|
|
8903
|
-
|
|
9117
|
+
session
|
|
8904
9118
|
};
|
|
8905
9119
|
});
|
|
8906
9120
|
}
|
|
@@ -8946,17 +9160,6 @@ class SqliteAuthStorage {
|
|
|
8946
9160
|
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
|
|
8947
9161
|
};
|
|
8948
9162
|
}
|
|
8949
|
-
if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && hasActiveUsers(this.db, projectId)) {
|
|
8950
|
-
consumeMagicLink(this.db, {
|
|
8951
|
-
projectId,
|
|
8952
|
-
id: input.magicLinkId,
|
|
8953
|
-
tokenHash: input.tokenHash,
|
|
8954
|
-
consumedAt: completedAt
|
|
8955
|
-
});
|
|
8956
|
-
return {
|
|
8957
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
|
|
8958
|
-
};
|
|
8959
|
-
}
|
|
8960
9163
|
validateCompleteSessionInput(this.db, projectId, input.session);
|
|
8961
9164
|
assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience);
|
|
8962
9165
|
let newUserId;
|
|
@@ -8986,8 +9189,9 @@ class SqliteAuthStorage {
|
|
|
8986
9189
|
projectId,
|
|
8987
9190
|
user
|
|
8988
9191
|
});
|
|
8989
|
-
const groupMemberships = this.
|
|
9192
|
+
const groupMemberships = this.applyGroups({
|
|
8990
9193
|
completedAt,
|
|
9194
|
+
source: "manual",
|
|
8991
9195
|
existing: invitation.groupMemberships,
|
|
8992
9196
|
groupIds: manualGroupIds,
|
|
8993
9197
|
projectId,
|
|
@@ -9041,36 +9245,18 @@ class SqliteAuthStorage {
|
|
|
9041
9245
|
error: new AuthStorageError11("missing_user", `[Sixb] User '${identity.user_id}' not found for linked OIDC identity.`)
|
|
9042
9246
|
};
|
|
9043
9247
|
}
|
|
9044
|
-
if (!identity && userRow && (!input.autoLinkByVerifiedEmail || !input.emailVerified)) {
|
|
9045
|
-
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9046
|
-
return {
|
|
9047
|
-
error: new AuthStorageError11("email_link_not_allowed", `[Sixb] OIDC identity cannot auto-link to user '${userRow.id}' for project '${projectId}'.`)
|
|
9048
|
-
};
|
|
9049
|
-
}
|
|
9050
9248
|
if (userRow?.status === "suspended") {
|
|
9051
9249
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9052
9250
|
return {
|
|
9053
9251
|
error: new AuthStorageError11("suspended_user", `[Sixb] User '${userRow.id}' is suspended for project '${projectId}'.`)
|
|
9054
9252
|
};
|
|
9055
9253
|
}
|
|
9056
|
-
if (shouldCreateUser && !input.emailVerified) {
|
|
9057
|
-
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9058
|
-
return {
|
|
9059
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
9060
|
-
};
|
|
9061
|
-
}
|
|
9062
9254
|
if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
|
|
9063
9255
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9064
9256
|
return {
|
|
9065
9257
|
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
9066
9258
|
};
|
|
9067
9259
|
}
|
|
9068
|
-
if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && hasActiveUsers(this.db, projectId)) {
|
|
9069
|
-
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9070
|
-
return {
|
|
9071
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
9072
|
-
};
|
|
9073
|
-
}
|
|
9074
9260
|
validateCompleteSessionInput(this.db, projectId, input.session);
|
|
9075
9261
|
assertSignInSessionAudience(projectId, input.session.audience, attempt.audience);
|
|
9076
9262
|
let newUserId;
|
|
@@ -9108,10 +9294,17 @@ class SqliteAuthStorage {
|
|
|
9108
9294
|
projectId,
|
|
9109
9295
|
user
|
|
9110
9296
|
});
|
|
9111
|
-
const groupMemberships = this.
|
|
9297
|
+
const groupMemberships = this.syncDirectoryGroups({
|
|
9112
9298
|
completedAt,
|
|
9113
|
-
existing:
|
|
9114
|
-
|
|
9299
|
+
existing: this.applyGroups({
|
|
9300
|
+
completedAt,
|
|
9301
|
+
source: "manual",
|
|
9302
|
+
existing: invitation.groupMemberships,
|
|
9303
|
+
groupIds: manualGroupIds,
|
|
9304
|
+
projectId,
|
|
9305
|
+
userId: user.id
|
|
9306
|
+
}),
|
|
9307
|
+
groupIds: input.directoryGroupIds,
|
|
9115
9308
|
projectId,
|
|
9116
9309
|
userId: user.id
|
|
9117
9310
|
});
|
|
@@ -9251,19 +9444,33 @@ class SqliteAuthStorage {
|
|
|
9251
9444
|
}));
|
|
9252
9445
|
return { invitation, groupMemberships };
|
|
9253
9446
|
}
|
|
9254
|
-
|
|
9447
|
+
applyGroups(input) {
|
|
9255
9448
|
const groupMemberships = [...input.existing];
|
|
9256
9449
|
for (const groupId of input.groupIds) {
|
|
9257
9450
|
groupMemberships.push(upsertGroupMembership(this.db, {
|
|
9258
9451
|
projectId: input.projectId,
|
|
9259
9452
|
userId: input.userId,
|
|
9260
9453
|
groupId,
|
|
9261
|
-
source:
|
|
9454
|
+
source: input.source,
|
|
9262
9455
|
createdAt: input.completedAt
|
|
9263
9456
|
}));
|
|
9264
9457
|
}
|
|
9265
9458
|
return groupMemberships;
|
|
9266
9459
|
}
|
|
9460
|
+
syncDirectoryGroups(input) {
|
|
9461
|
+
if (!input.groupIds) {
|
|
9462
|
+
return input.existing;
|
|
9463
|
+
}
|
|
9464
|
+
const groupIds = normalizeGroupIds(input.groupIds);
|
|
9465
|
+
this.db.query(`
|
|
9466
|
+
DELETE FROM auth_group_memberships
|
|
9467
|
+
WHERE project_id = ?
|
|
9468
|
+
AND user_id = ?
|
|
9469
|
+
AND source = 'directory'
|
|
9470
|
+
AND group_id NOT IN (${groupIds.map(() => "?").join(", ")})
|
|
9471
|
+
`).run(input.projectId, input.userId, ...groupIds);
|
|
9472
|
+
return this.applyGroups({ ...input, groupIds, source: "directory" });
|
|
9473
|
+
}
|
|
9267
9474
|
createSignInSession(input) {
|
|
9268
9475
|
return createSession(this.db, {
|
|
9269
9476
|
id: input.session.id,
|
|
@@ -9315,16 +9522,6 @@ class SqliteAuthStorage {
|
|
|
9315
9522
|
});
|
|
9316
9523
|
}
|
|
9317
9524
|
}
|
|
9318
|
-
function hasActiveUsers(db, projectId) {
|
|
9319
|
-
const row = db.query(`
|
|
9320
|
-
SELECT 1 AS active
|
|
9321
|
-
FROM auth_users
|
|
9322
|
-
WHERE project_id = ?
|
|
9323
|
-
AND status = 'active'
|
|
9324
|
-
LIMIT 1
|
|
9325
|
-
`).get(projectId);
|
|
9326
|
-
return row !== null;
|
|
9327
|
-
}
|
|
9328
9525
|
function assertSignInSessionAudience(projectId, sessionAudience, storedAudience) {
|
|
9329
9526
|
if (sessionAudience === storedAudience) {
|
|
9330
9527
|
return;
|
|
@@ -9341,7 +9538,7 @@ function assertCompletableDeviceAuthorization(authorization, input) {
|
|
|
9341
9538
|
if (authorization.status === "denied") {
|
|
9342
9539
|
throw new AuthStorageError11("device_authorization_denied", "[Sixb] Device authorization was denied.");
|
|
9343
9540
|
}
|
|
9344
|
-
if (authorization.status !== "approved" || authorization.expiresAt <= input.completedAt || !authorization.approvedUserId || !authorization.approvedSessionId || input.
|
|
9541
|
+
if (authorization.status !== "approved" || authorization.expiresAt <= input.completedAt || !authorization.approvedUserId || !authorization.approvedSessionId || input.session.projectId !== input.projectId || input.session.userId !== authorization.approvedUserId) {
|
|
9345
9542
|
throw new AuthStorageError11("invalid_device_authorization", "[Sixb] Device authorization is invalid.");
|
|
9346
9543
|
}
|
|
9347
9544
|
}
|
|
@@ -9920,6 +10117,100 @@ function isDuplicateExecutionError(error) {
|
|
|
9920
10117
|
return error instanceof Error && error.message.includes("UNIQUE constraint failed: executions.project_id, executions.id");
|
|
9921
10118
|
}
|
|
9922
10119
|
|
|
10120
|
+
// src/file-upload-session-storage.ts
|
|
10121
|
+
import {
|
|
10122
|
+
DurableFileUploadSessions,
|
|
10123
|
+
fileUploadSessionReapAt,
|
|
10124
|
+
parseFileUploadSessionRow
|
|
10125
|
+
} from "@sixb/core/internal/file-upload-session-storage-provider";
|
|
10126
|
+
class SqliteFileUploadSessionStorage extends DurableFileUploadSessions {
|
|
10127
|
+
constructor(connection) {
|
|
10128
|
+
super(new SqliteFileUploadSessionBackend(connection.db));
|
|
10129
|
+
}
|
|
10130
|
+
}
|
|
10131
|
+
|
|
10132
|
+
class SqliteFileUploadSessionBackend {
|
|
10133
|
+
db;
|
|
10134
|
+
persistence;
|
|
10135
|
+
constructor(db) {
|
|
10136
|
+
this.db = db;
|
|
10137
|
+
this.persistence = new SqliteFileUploadSessionPersistence(db);
|
|
10138
|
+
}
|
|
10139
|
+
read(run) {
|
|
10140
|
+
return run(this.persistence);
|
|
10141
|
+
}
|
|
10142
|
+
transaction(run) {
|
|
10143
|
+
return runImmediateTransactionAsync(this.db, () => run(this.persistence));
|
|
10144
|
+
}
|
|
10145
|
+
}
|
|
10146
|
+
|
|
10147
|
+
class SqliteFileUploadSessionPersistence {
|
|
10148
|
+
db;
|
|
10149
|
+
constructor(db) {
|
|
10150
|
+
this.db = db;
|
|
10151
|
+
}
|
|
10152
|
+
async now() {
|
|
10153
|
+
const row = this.db.query("SELECT strftime('%Y-%m-%dT%H:%M:%fZ', 'now') AS now").get();
|
|
10154
|
+
return new Date(row.now);
|
|
10155
|
+
}
|
|
10156
|
+
async insert(session) {
|
|
10157
|
+
try {
|
|
10158
|
+
this.db.query(`INSERT INTO file_upload_sessions (
|
|
10159
|
+
id, project_id, owner_principal_type, owner_principal_id, strategy, status, file_name,
|
|
10160
|
+
media_type, logical_path, expected_size_bytes, expected_digest, provider_upload, signed_parts, file_ref,
|
|
10161
|
+
created_at, expires_at, completed_at, aborted_at, reap_at
|
|
10162
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(session.id, session.projectId, session.principal.type, session.principal.id, session.strategy, session.status, session.fileName ?? null, session.mediaType ?? null, session.logicalPath ?? null, session.expectedSizeBytes ?? null, session.expectedDigest ?? null, ...mutableColumns(session));
|
|
10163
|
+
return true;
|
|
10164
|
+
} catch (error) {
|
|
10165
|
+
if (isUniqueConstraintError2(error))
|
|
10166
|
+
return false;
|
|
10167
|
+
throw error;
|
|
10168
|
+
}
|
|
10169
|
+
}
|
|
10170
|
+
async get(uploadId) {
|
|
10171
|
+
const row = this.db.query(`${SELECT_SESSION} WHERE id = ?`).get(uploadId);
|
|
10172
|
+
return row ? parseFileUploadSessionRow(row) : null;
|
|
10173
|
+
}
|
|
10174
|
+
async update(session) {
|
|
10175
|
+
this.db.query(`UPDATE file_upload_sessions
|
|
10176
|
+
SET status = ?, provider_upload = ?, signed_parts = ?, file_ref = ?, created_at = ?,
|
|
10177
|
+
expires_at = ?, completed_at = ?, aborted_at = ?, reap_at = ?
|
|
10178
|
+
WHERE id = ?`).run(session.status, ...mutableColumns(session), session.id);
|
|
10179
|
+
}
|
|
10180
|
+
async listAbandoned(input) {
|
|
10181
|
+
const rows = this.db.query(`${SELECT_SESSION}
|
|
10182
|
+
WHERE project_id = ? AND status = 'pending' AND provider_upload IS NOT NULL
|
|
10183
|
+
AND expires_at <= ?
|
|
10184
|
+
ORDER BY expires_at, id
|
|
10185
|
+
LIMIT ?`).all(input.projectId, input.now.toISOString(), input.limit);
|
|
10186
|
+
return rows.map((row) => parseFileUploadSessionRow(row));
|
|
10187
|
+
}
|
|
10188
|
+
async deleteReapable(now) {
|
|
10189
|
+
return this.db.query("DELETE FROM file_upload_sessions WHERE reap_at IS NOT NULL AND reap_at <= ?").run(now.toISOString()).changes;
|
|
10190
|
+
}
|
|
10191
|
+
}
|
|
10192
|
+
var SELECT_SESSION = `
|
|
10193
|
+
SELECT id, project_id AS projectId, owner_principal_type AS ownerPrincipalType,
|
|
10194
|
+
owner_principal_id AS ownerPrincipalId, strategy, status,
|
|
10195
|
+
file_name AS fileName, media_type AS mediaType, logical_path AS logicalPath,
|
|
10196
|
+
expected_size_bytes AS expectedSizeBytes, expected_digest AS expectedDigest,
|
|
10197
|
+
provider_upload AS providerUpload, signed_parts AS signedParts, file_ref AS fileRef,
|
|
10198
|
+
created_at AS createdAt, expires_at AS expiresAt, completed_at AS completedAt,
|
|
10199
|
+
aborted_at AS abortedAt
|
|
10200
|
+
FROM file_upload_sessions`;
|
|
10201
|
+
function mutableColumns(session) {
|
|
10202
|
+
return [
|
|
10203
|
+
session.providerUpload === undefined ? null : JSON.stringify(session.providerUpload),
|
|
10204
|
+
JSON.stringify(session.signedParts),
|
|
10205
|
+
session.fileRef === undefined ? null : JSON.stringify(session.fileRef),
|
|
10206
|
+
session.createdAt.toISOString(),
|
|
10207
|
+
session.expiresAt.toISOString(),
|
|
10208
|
+
session.completedAt?.toISOString() ?? null,
|
|
10209
|
+
session.abortedAt?.toISOString() ?? null,
|
|
10210
|
+
fileUploadSessionReapAt(session)?.toISOString() ?? null
|
|
10211
|
+
];
|
|
10212
|
+
}
|
|
10213
|
+
|
|
9923
10214
|
// src/objects/storage.ts
|
|
9924
10215
|
import { hasVectorProfile as hasVectorProfile3 } from "@sixb/core/internal/query";
|
|
9925
10216
|
|
|
@@ -10554,9 +10845,9 @@ function compilePredicate(predicate) {
|
|
|
10554
10845
|
return negatePredicate(item);
|
|
10555
10846
|
}
|
|
10556
10847
|
case "eq":
|
|
10557
|
-
return compileEqualityPredicate(predicate.propertyId, "eq", predicate.value);
|
|
10848
|
+
return predicate.scalarKind === "userRef" ? compileUserRefInPredicate(predicate.propertyId, [predicate.value]) : compileEqualityPredicate(predicate.propertyId, "eq", predicate.value);
|
|
10558
10849
|
case "neq":
|
|
10559
|
-
return compileEqualityPredicate(predicate.propertyId, "neq", predicate.value);
|
|
10850
|
+
return predicate.scalarKind === "userRef" ? negatePredicate(compileUserRefInPredicate(predicate.propertyId, [predicate.value])) : compileEqualityPredicate(predicate.propertyId, "neq", predicate.value);
|
|
10560
10851
|
case "lt":
|
|
10561
10852
|
case "lte":
|
|
10562
10853
|
case "gt":
|
|
@@ -10568,16 +10859,52 @@ function compilePredicate(predicate) {
|
|
|
10568
10859
|
};
|
|
10569
10860
|
}
|
|
10570
10861
|
case "in":
|
|
10571
|
-
return compileInPredicate(predicate.propertyId, predicate.values);
|
|
10862
|
+
return predicate.scalarKind === "userRef" ? compileUserRefInPredicate(predicate.propertyId, predicate.values) : compileInPredicate(predicate.propertyId, predicate.values);
|
|
10572
10863
|
case "exists": {
|
|
10573
10864
|
const sql = `${jsonTypeExpression()} IS NOT NULL`;
|
|
10574
10865
|
const item = { sql, args: [jsonPath(predicate.propertyId)] };
|
|
10575
10866
|
return predicate.value ? item : negatePredicate(item);
|
|
10576
10867
|
}
|
|
10577
10868
|
case "contains":
|
|
10578
|
-
return compileContainsPredicate(predicate.propertyId, predicate.value);
|
|
10869
|
+
return predicate.scalarKind === "userRef" ? compileUserRefContainsPredicate(predicate.propertyId, predicate.value) : compileContainsPredicate(predicate.propertyId, predicate.value);
|
|
10579
10870
|
}
|
|
10580
10871
|
}
|
|
10872
|
+
function compileUserRefInPredicate(propertyId, values) {
|
|
10873
|
+
const path = jsonPath(propertyId);
|
|
10874
|
+
const ids = values.flatMap((value) => {
|
|
10875
|
+
const id = userRefId(value);
|
|
10876
|
+
return id === undefined ? [] : [id];
|
|
10877
|
+
});
|
|
10878
|
+
const clauses = [];
|
|
10879
|
+
const args = [];
|
|
10880
|
+
if (values.includes(null)) {
|
|
10881
|
+
clauses.push(`${jsonTypeExpression()} = 'null'`);
|
|
10882
|
+
args.push(path);
|
|
10883
|
+
}
|
|
10884
|
+
if (ids.length > 0) {
|
|
10885
|
+
clauses.push(`(${jsonValueExpression()} = 'user' AND ${jsonValueExpression()} IN (${ids.map(() => "?").join(", ")}))`);
|
|
10886
|
+
args.push(`${path}.type`, `${path}.id`, ...ids);
|
|
10887
|
+
}
|
|
10888
|
+
if (clauses.length === 0)
|
|
10889
|
+
return { sql: "0 = 1", args: [] };
|
|
10890
|
+
return { sql: `(${clauses.join(" OR ")})`, args };
|
|
10891
|
+
}
|
|
10892
|
+
function compileUserRefContainsPredicate(propertyId, value) {
|
|
10893
|
+
const id = userRefId(value);
|
|
10894
|
+
if (id === undefined)
|
|
10895
|
+
return { sql: "0 = 1", args: [] };
|
|
10896
|
+
const path = jsonPath(propertyId);
|
|
10897
|
+
return {
|
|
10898
|
+
sql: `(${jsonTypeExpression()} = 'array' AND EXISTS (SELECT 1 FROM json_each(properties, ?) AS item WHERE item.type = 'object' AND json_extract(item.value, '$.type') = 'user' AND json_extract(item.value, '$.id') = ?))`,
|
|
10899
|
+
args: [path, path, id]
|
|
10900
|
+
};
|
|
10901
|
+
}
|
|
10902
|
+
function userRefId(value) {
|
|
10903
|
+
if (typeof value !== "object" || value === null)
|
|
10904
|
+
return;
|
|
10905
|
+
const { type, id } = value;
|
|
10906
|
+
return type === "user" && typeof id === "string" ? id : undefined;
|
|
10907
|
+
}
|
|
10581
10908
|
function negatePredicate(predicate) {
|
|
10582
10909
|
return {
|
|
10583
10910
|
sql: `(NOT COALESCE((${predicate.sql}), 0))`,
|
|
@@ -11308,6 +11635,9 @@ function sqliteFacetValue(valueType, value) {
|
|
|
11308
11635
|
return false;
|
|
11309
11636
|
if (valueType === "null")
|
|
11310
11637
|
return null;
|
|
11638
|
+
if ((valueType === "object" || valueType === "array") && typeof value === "string") {
|
|
11639
|
+
return JSON.parse(value);
|
|
11640
|
+
}
|
|
11311
11641
|
return value;
|
|
11312
11642
|
}
|
|
11313
11643
|
function existsProbeQuery(query) {
|
|
@@ -11744,7 +12074,8 @@ var SQLITE_OBJECT_QUERY_CAPABILITIES = {
|
|
|
11744
12074
|
double: { equality: true, ordering: true },
|
|
11745
12075
|
decimal: { equality: true },
|
|
11746
12076
|
date: { equality: true, ordering: true },
|
|
11747
|
-
timestamp: { equality: true, ordering: true }
|
|
12077
|
+
timestamp: { equality: true, ordering: true },
|
|
12078
|
+
userRef: { equality: true }
|
|
11748
12079
|
},
|
|
11749
12080
|
limits: {
|
|
11750
12081
|
totalCount: true,
|
|
@@ -11938,7 +12269,10 @@ function commitRecord(row) {
|
|
|
11938
12269
|
requestHash: row.request_hash,
|
|
11939
12270
|
executionId: row.execution_id,
|
|
11940
12271
|
origin: parseJson2(row.origin),
|
|
11941
|
-
...row.
|
|
12272
|
+
...row.requested_by === null ? {} : {
|
|
12273
|
+
requestedBy: parseJson2(row.requested_by)
|
|
12274
|
+
},
|
|
12275
|
+
executor: parseJson2(row.executor),
|
|
11942
12276
|
ontologyRevision: row.ontology_revision,
|
|
11943
12277
|
...row.projection_revision === null ? {} : { projectionRevision: row.projection_revision },
|
|
11944
12278
|
...row.ownership_hash === null ? {} : { ownershipHash: row.ownership_hash },
|
|
@@ -12092,6 +12426,9 @@ import {
|
|
|
12092
12426
|
telemetryPointSortKey as telemetryPointSortKey2
|
|
12093
12427
|
} from "@sixb/core/internal/materialization";
|
|
12094
12428
|
import {
|
|
12429
|
+
assertExpectedLinkRevision,
|
|
12430
|
+
assertExpectedLinkScopeRevision,
|
|
12431
|
+
assertExpectedObjectRevision,
|
|
12095
12432
|
assertMaterializationFinalizationCorrelation,
|
|
12096
12433
|
assertMaterializationHeader,
|
|
12097
12434
|
assertMaterializationLaneCompletion,
|
|
@@ -13587,15 +13924,15 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13587
13924
|
const reader = new SqliteMaterializationStateReader(this.db, input.commit.projectId);
|
|
13588
13925
|
for (const expected of input.expected.sources)
|
|
13589
13926
|
this.assertSource(expected, input.commit.projectId);
|
|
13590
|
-
for (const expected of input.expected.objects)
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13927
|
+
for (const expected of input.expected.objects) {
|
|
13928
|
+
assertExpectedObjectRevision(reader.effectiveObjectRevision(expected.ref), expected);
|
|
13929
|
+
}
|
|
13930
|
+
for (const expected of input.expected.links) {
|
|
13931
|
+
assertExpectedLinkRevision(reader.effectiveLinkLastCommit(expected.ref), expected);
|
|
13932
|
+
}
|
|
13594
13933
|
const linkScopeRevisions = reader.linkScopeRevisions(input.expected.linkScopes);
|
|
13595
13934
|
for (const [index, expected] of input.expected.linkScopes.entries()) {
|
|
13596
|
-
|
|
13597
|
-
throw new MaterializationConflictError6("effective-state", `Expected link scope changed for ${expected.source.objectTypeId}:${expected.source.primaryId}.${expected.linkId}.`);
|
|
13598
|
-
}
|
|
13935
|
+
assertExpectedLinkScopeRevision(linkScopeRevisions[index]?.fingerprint, expected);
|
|
13599
13936
|
}
|
|
13600
13937
|
for (const expected of input.expected.points) {
|
|
13601
13938
|
const point = reader.exactPoint(expected.series, expected.at);
|
|
@@ -13828,30 +14165,6 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13828
14165
|
throw new MaterializationConflictError6("projection-fence", `Source '${expected.source.projectionId}' changed.`);
|
|
13829
14166
|
}
|
|
13830
14167
|
}
|
|
13831
|
-
assertObject(reader, expected) {
|
|
13832
|
-
const row = reader.effectiveObjectRevision(expected.ref);
|
|
13833
|
-
if (!expected.exists) {
|
|
13834
|
-
if (row) {
|
|
13835
|
-
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} to be absent.`);
|
|
13836
|
-
}
|
|
13837
|
-
return;
|
|
13838
|
-
}
|
|
13839
|
-
if (!row || row.version !== expected.version || row.lastCommitId !== expected.lastCommitId) {
|
|
13840
|
-
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} changed.`);
|
|
13841
|
-
}
|
|
13842
|
-
}
|
|
13843
|
-
assertLink(reader, expected) {
|
|
13844
|
-
const lastCommitId = reader.effectiveLinkLastCommit(expected.ref);
|
|
13845
|
-
if (!expected.exists) {
|
|
13846
|
-
if (lastCommitId !== undefined) {
|
|
13847
|
-
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} to be absent.`);
|
|
13848
|
-
}
|
|
13849
|
-
return;
|
|
13850
|
-
}
|
|
13851
|
-
if (lastCommitId === undefined || lastCommitId !== expected.lastCommitId) {
|
|
13852
|
-
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} changed.`);
|
|
13853
|
-
}
|
|
13854
|
-
}
|
|
13855
14168
|
async assertFinalization(session, input) {
|
|
13856
14169
|
const { commit } = session.header;
|
|
13857
14170
|
const { result } = input.finalization;
|
|
@@ -13988,11 +14301,13 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13988
14301
|
this.db.query(`
|
|
13989
14302
|
INSERT INTO ontology_commits (
|
|
13990
14303
|
project_id, id, idempotency_key, request_hash, execution_id,
|
|
13991
|
-
origin_kind, origin_run_id, origin_batch_ordinal, origin,
|
|
14304
|
+
origin_kind, origin_run_id, origin_batch_ordinal, origin, requested_by, executor,
|
|
13992
14305
|
ontology_revision, projection_revision, ownership_hash,
|
|
13993
14306
|
intent, result, committed_at
|
|
13994
|
-
) VALUES (
|
|
13995
|
-
|
|
14307
|
+
) VALUES (
|
|
14308
|
+
?, ?, ?, ?, ?, ?, ?, ?, json(?), json(?), json(?), ?, ?, ?, json(?), json(?), ?
|
|
14309
|
+
)
|
|
14310
|
+
`).run(commit.projectId, commit.id, commit.idempotencyKey, commit.requestHash, commit.executionId, origin.kind, origin.runId, origin.batchOrdinal, canonicalJson(commit.origin), commit.requestedBy === undefined ? null : canonicalJson(commit.requestedBy), canonicalJson(commit.executor), commit.ontologyRevision, commit.projectionRevision ?? null, commit.ownershipHash ?? null, canonicalJson(commit.intent), canonicalJson(input.finalization.result), commit.committedAt);
|
|
13996
14311
|
} catch (error) {
|
|
13997
14312
|
if (isSqliteConstraintError(error)) {
|
|
13998
14313
|
throw new MaterializationConflictError6("idempotency", "Ontology commit identity already exists.");
|
|
@@ -16620,7 +16935,7 @@ class SqliteWorkflowInterventionStorage {
|
|
|
16620
16935
|
submitted_by = ?,
|
|
16621
16936
|
response = ?
|
|
16622
16937
|
WHERE project_id = ? AND id = ?
|
|
16623
|
-
`).run("submitted", (input.submittedAt ?? new Date).toISOString(),
|
|
16938
|
+
`).run("submitted", (input.submittedAt ?? new Date).toISOString(), serializePrincipal(input.submittedBy), serializeRecord(input.response), existing.project_id, existing.id);
|
|
16624
16939
|
return this.requireIntervention(input.projectId, input.id);
|
|
16625
16940
|
})();
|
|
16626
16941
|
}
|
|
@@ -16634,7 +16949,7 @@ class SqliteWorkflowInterventionStorage {
|
|
|
16634
16949
|
cancelled_at = ?,
|
|
16635
16950
|
cancelled_by = ?
|
|
16636
16951
|
WHERE project_id = ? AND id = ?
|
|
16637
|
-
`).run("cancelled", (input.cancelledAt ?? new Date).toISOString(),
|
|
16952
|
+
`).run("cancelled", (input.cancelledAt ?? new Date).toISOString(), serializePrincipal(input.cancelledBy), existing.project_id, existing.id);
|
|
16638
16953
|
return this.requireIntervention(input.projectId, input.id);
|
|
16639
16954
|
})();
|
|
16640
16955
|
}
|
|
@@ -16756,10 +17071,10 @@ function serializeRecord(value) {
|
|
|
16756
17071
|
function parseRecord(value) {
|
|
16757
17072
|
return JSON.parse(value);
|
|
16758
17073
|
}
|
|
16759
|
-
function
|
|
16760
|
-
return
|
|
17074
|
+
function serializePrincipal(principal) {
|
|
17075
|
+
return principal ? JSON.stringify(principal) : null;
|
|
16761
17076
|
}
|
|
16762
|
-
function
|
|
17077
|
+
function parsePrincipal(value) {
|
|
16763
17078
|
return value ? JSON.parse(value) : undefined;
|
|
16764
17079
|
}
|
|
16765
17080
|
function rowToWorkflowInterventionRecord(row) {
|
|
@@ -16779,10 +17094,10 @@ function rowToWorkflowInterventionRecord(row) {
|
|
|
16779
17094
|
requestedAt: new Date(row.requested_at),
|
|
16780
17095
|
expiresAt: row.expires_at ? new Date(row.expires_at) : undefined,
|
|
16781
17096
|
submittedAt: row.submitted_at ? new Date(row.submitted_at) : undefined,
|
|
16782
|
-
submittedBy:
|
|
17097
|
+
submittedBy: parsePrincipal(row.submitted_by),
|
|
16783
17098
|
response: row.response ? parseRecord(row.response) : undefined,
|
|
16784
17099
|
cancelledAt: row.cancelled_at ? new Date(row.cancelled_at) : undefined,
|
|
16785
|
-
cancelledBy:
|
|
17100
|
+
cancelledBy: parsePrincipal(row.cancelled_by),
|
|
16786
17101
|
expiredAt: row.expired_at ? new Date(row.expired_at) : undefined
|
|
16787
17102
|
};
|
|
16788
17103
|
}
|
|
@@ -17445,6 +17760,7 @@ class SqliteStorage {
|
|
|
17445
17760
|
shareGrants;
|
|
17446
17761
|
shareSessions;
|
|
17447
17762
|
connectorConnections;
|
|
17763
|
+
fileUploadSessions;
|
|
17448
17764
|
migrators;
|
|
17449
17765
|
connection;
|
|
17450
17766
|
readConnection;
|
|
@@ -17495,6 +17811,7 @@ class SqliteStorage {
|
|
|
17495
17811
|
this.shareGrants = createOperationScopedFacade(stores.shareGrants, scope);
|
|
17496
17812
|
this.shareSessions = createOperationScopedFacade(stores.shareSessions, scope);
|
|
17497
17813
|
this.connectorConnections = createOperationScopedFacade(stores.connectorConnections, scope);
|
|
17814
|
+
this.fileUploadSessions = createOperationScopedFacade(stores.fileUploadSessions, scope);
|
|
17498
17815
|
this.migrators = options.path ? createSqliteStorageMigrators(options.path) : [];
|
|
17499
17816
|
registerSqliteStorageTestingAdapter(this, (durationMs) => stores.connectorConnections.advanceTimeForTesting(durationMs));
|
|
17500
17817
|
}
|
|
@@ -17619,7 +17936,8 @@ function createSqliteStores(connection, options) {
|
|
|
17619
17936
|
rules: new SqliteRulesStorage({ connection }),
|
|
17620
17937
|
shareGrants,
|
|
17621
17938
|
shareSessions,
|
|
17622
|
-
connectorConnections: new SqliteConnectorConnectionStorage(connection)
|
|
17939
|
+
connectorConnections: new SqliteConnectorConnectionStorage(connection),
|
|
17940
|
+
fileUploadSessions: new SqliteFileUploadSessionStorage(connection)
|
|
17623
17941
|
};
|
|
17624
17942
|
}
|
|
17625
17943
|
export {
|
|
@@ -17627,4 +17945,4 @@ export {
|
|
|
17627
17945
|
migrateSqliteStorage
|
|
17628
17946
|
};
|
|
17629
17947
|
|
|
17630
|
-
//# debugId=
|
|
17948
|
+
//# debugId=9F25188860B63C3464756E2164756E21
|