or3-provider-sqlite 0.0.11 → 0.0.13
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/README.md +36 -12
- package/dist/module.json +1 -1
- package/dist/runtime/server/plugins/register.js +3 -1
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.d.ts +2 -0
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.js +17 -1
- package/dist/runtime/server/sync/workspace-item-capability.d.ts +4 -0
- package/dist/runtime/server/sync/workspace-item-capability.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,19 +127,27 @@ OR3_CONNECT_PROVIDER=sqlite
|
|
|
127
127
|
|
|
128
128
|
On server startup, the Nitro plugin:
|
|
129
129
|
|
|
130
|
-
1.
|
|
131
|
-
2.
|
|
132
|
-
3. Registers `
|
|
133
|
-
4. Registers `
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
7. Registers `RateLimitProvider` with ID `sqlite` and the sync admin adapter
|
|
138
|
-
8. Registers a durable `BackgroundJobProvider` with ID `sqlite`, so active chat
|
|
130
|
+
1. Registers `AuthWorkspaceStore` with ID `sqlite`
|
|
131
|
+
2. Registers `SyncGatewayAdapter` when SQLite conversation transfer is enabled
|
|
132
|
+
3. Registers `ConnectStore` when SQLite Connect persistence is selected
|
|
133
|
+
4. Registers `WebhookStore` on runtimes other than D1, and the admin store
|
|
134
|
+
provider (D1 declares server-side admin capabilities unavailable)
|
|
135
|
+
5. Registers `RateLimitProvider` with ID `sqlite` and the sync admin adapter
|
|
136
|
+
6. Registers a durable `BackgroundJobProvider` with ID `sqlite`, so active chat
|
|
139
137
|
and workflow jobs remain addressable across Nitro HMR/module reloads
|
|
138
|
+
7. Initializes the database and runs schema migrations eagerly on local
|
|
139
|
+
runtimes, or on the first Worker request for D1
|
|
140
140
|
|
|
141
|
-
Registration is skipped when `auth.enabled` is `false`, or when
|
|
142
|
-
|
|
141
|
+
Registration is skipped when `auth.enabled` is `false`, or when SQLite is not
|
|
142
|
+
selected for account workspaces, Connect or background jobs. Account workspaces
|
|
143
|
+
use `sync.provider` even with `sync.enabled=false`; disabling conversation
|
|
144
|
+
transfer does not disable the workspace store. The sync gateway remains
|
|
145
|
+
unregistered while transfer is disabled.
|
|
146
|
+
|
|
147
|
+
Workspace-only account storage requires `or3-provider-sqlite@0.0.12` or later.
|
|
148
|
+
Version `0.0.11` skips workspace-store registration when sync, Connect and
|
|
149
|
+
background jobs are all disabled, causing Chat to exit before listening.
|
|
150
|
+
Upgrade the provider; conversation transfer can remain disabled.
|
|
143
151
|
|
|
144
152
|
SQLite background jobs also persist mixed-runtime tool handoffs. Browser-only
|
|
145
153
|
calls are atomically claimed and settled in `execution_json`; parked calls are
|
|
@@ -254,7 +262,7 @@ bun run build # build for distribution
|
|
|
254
262
|
|
|
255
263
|
### Atomic workspace settings
|
|
256
264
|
|
|
257
|
-
The workspace settings store implements `compareAndSet(workspaceId, key, expectedValue, nextValue)` atomically. A `null` expected value means the key must be absent; a conflict returns `false` without overwriting it. The host uses this for concurrent plugin setup saves and persistent AI spend reservations.
|
|
265
|
+
The workspace settings store implements `compareAndSet(workspaceId, key, expectedValue, nextValue)` atomically. A `null` expected value means the key must be absent; a conflict returns `false` without overwriting it. The host uses this for concurrent plugin setup saves, bulk plugin enablement and permission review, and persistent AI spend reservations. A provider without this operation cannot safely run a bulk plugin rollout. A slow plugin write may outlive the HTTP response: the host reports pending after eight seconds and keeps the plugin lock until the atomic operation settles.
|
|
258
266
|
|
|
259
267
|
|
|
260
268
|
### Testing local changes in OR3 Chat
|
|
@@ -265,3 +273,19 @@ prints its selected path; restart it after provider edits. Missing repositories
|
|
|
265
273
|
or failed builds fall back to installed packages with a warning.
|
|
266
274
|
`OR3_LOCAL_PROVIDERS=false` disables local selection. Production builds use the
|
|
267
275
|
installed package, so local development does not publish these changes.
|
|
276
|
+
|
|
277
|
+
## Workspace Files and Trash admission
|
|
278
|
+
|
|
279
|
+
Native synchronous adapters advertise workspace-item capability v1. Canonical
|
|
280
|
+
posts/projects with catalog, logical Trash, or file-membership semantics require
|
|
281
|
+
a current writer even when the incoming update omits those fields. Admission is
|
|
282
|
+
checked inside the same write transaction, before versions or changes are
|
|
283
|
+
allocated. Canonical retained post references include logically trashed catalog
|
|
284
|
+
entries/documents and checkpoints. D1 does not advertise this transactional
|
|
285
|
+
capability.
|
|
286
|
+
|
|
287
|
+
This source change is a provider publication dependency for the host feature.
|
|
288
|
+
Rebuild the provider before testing a file-linked host; follow the existing OR3
|
|
289
|
+
release/update workflow separately. Local qualification does not authorize
|
|
290
|
+
publication or a production migration. Files cloud exposure remains gated until
|
|
291
|
+
the supported profile is qualified.
|
package/dist/module.json
CHANGED
|
@@ -29,10 +29,12 @@ let d1RuntimeInitialization;
|
|
|
29
29
|
export default defineNitroPlugin(async (nitroApp) => {
|
|
30
30
|
const config = useRuntimeConfig();
|
|
31
31
|
if (!config.auth?.enabled) return;
|
|
32
|
+
const workspaceSelected = config.sync?.provider === SQLITE_PROVIDER_ID;
|
|
32
33
|
const syncSelected = config.sync?.enabled === true && config.sync?.provider === SQLITE_PROVIDER_ID;
|
|
33
34
|
const connectSelected = config.connect?.enabled === true && config.connect?.provider === SQLITE_PROVIDER_ID;
|
|
34
35
|
const backgroundJobsSelected = config.backgroundJobs?.enabled === true && config.backgroundJobs?.storageProvider === SQLITE_PROVIDER_ID;
|
|
35
|
-
if (!
|
|
36
|
+
if (!workspaceSelected && !connectSelected && !backgroundJobsSelected)
|
|
37
|
+
return;
|
|
36
38
|
const driver = getSqliteDriver();
|
|
37
39
|
if (driver === "d1" && connectSelected) {
|
|
38
40
|
throw new Error(
|
|
@@ -15,10 +15,12 @@ export declare class SqliteSyncGatewayAdapter implements SyncGatewayAdapter {
|
|
|
15
15
|
readonly snapshotBootstrap: "snapshot-v1";
|
|
16
16
|
readonly historyRetention: "snapshot-v1";
|
|
17
17
|
readonly backgroundGenerationHistory?: undefined;
|
|
18
|
+
readonly workspaceItems?: undefined;
|
|
18
19
|
} | {
|
|
19
20
|
readonly snapshotBootstrap: "snapshot-v1";
|
|
20
21
|
readonly historyRetention: "snapshot-v1";
|
|
21
22
|
readonly backgroundGenerationHistory: "v1";
|
|
23
|
+
readonly workspaceItems: "v1";
|
|
22
24
|
};
|
|
23
25
|
private get db();
|
|
24
26
|
private assertCanonicalActor;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasWorkspaceItemSemantics, WORKSPACE_ITEM_CAPABILITY } from "./workspace-item-capability.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { canRunSyncHistoryGc } from "./history-gc-policy.js";
|
|
3
4
|
import { createError } from "h3";
|
|
@@ -428,7 +429,8 @@ export class SqliteSyncGatewayAdapter {
|
|
|
428
429
|
} : {
|
|
429
430
|
snapshotBootstrap: "snapshot-v1",
|
|
430
431
|
historyRetention: "snapshot-v1",
|
|
431
|
-
backgroundGenerationHistory: "v1"
|
|
432
|
+
backgroundGenerationHistory: "v1",
|
|
433
|
+
workspaceItems: "v1"
|
|
432
434
|
};
|
|
433
435
|
}
|
|
434
436
|
get db() {
|
|
@@ -1068,6 +1070,20 @@ export class SqliteSyncGatewayAdapter {
|
|
|
1068
1070
|
let finalServerVersion = 0;
|
|
1069
1071
|
const hookEmissions = [];
|
|
1070
1072
|
const runTx = raw.transaction(() => {
|
|
1073
|
+
if (input.workspaceItemCapability !== WORKSPACE_ITEM_CAPABILITY) {
|
|
1074
|
+
for (const op of uniqueOps) {
|
|
1075
|
+
const table = SYNCED_TABLE_MAP[op.tableName];
|
|
1076
|
+
if (!table || !["posts", "projects"].includes(op.tableName)) continue;
|
|
1077
|
+
const canonical = raw.prepare("SELECT data_json FROM " + table + " WHERE id = ? AND workspace_id = ?").get(op.pk, workspaceId);
|
|
1078
|
+
if (hasWorkspaceItemSemantics(op.tableName, op.payload) || hasWorkspaceItemSemantics(op.tableName, canonical ? JSON.parse(canonical.data_json) : void 0)) {
|
|
1079
|
+
throw createError({
|
|
1080
|
+
statusCode: 426,
|
|
1081
|
+
statusMessage: "Update OR3 Chat to use workspace Files and Trash",
|
|
1082
|
+
data: { code: "OR3_WORKSPACE_ITEM_UPDATE_REQUIRED", requiredCapability: WORKSPACE_ITEM_CAPABILITY }
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1071
1087
|
const opIds = uniqueOps.map((o) => o.stamp.opId);
|
|
1072
1088
|
const existingOps = /* @__PURE__ */ new Map();
|
|
1073
1089
|
const chunkSize = 500;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const WORKSPACE_ITEM_CAPABILITY: "v1";
|
|
2
|
+
export type WorkspaceItemCapability = typeof WORKSPACE_ITEM_CAPABILITY;
|
|
3
|
+
/** Detect semantics even when their version/content is malformed or unknown. */
|
|
4
|
+
export declare function hasWorkspaceItemSemantics(tableName: string, payload: unknown): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const FILE_CATALOG_POST_TYPE = "or3:file";
|
|
2
|
+
const WORKSPACE_ITEM_META_KEY = "or3.workspace-item";
|
|
3
|
+
export const WORKSPACE_ITEM_CAPABILITY = "v1";
|
|
4
|
+
function decoded(value) {
|
|
5
|
+
if (typeof value !== "string") return value;
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(value);
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function hasWorkspaceItemSemantics(tableName, payload) {
|
|
13
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
14
|
+
const row = payload;
|
|
15
|
+
if (tableName === "posts") {
|
|
16
|
+
if ((row.post_type ?? row.postType) === FILE_CATALOG_POST_TYPE) return true;
|
|
17
|
+
const meta = decoded(row.meta);
|
|
18
|
+
if (Array.isArray(meta)) return meta.some((entry) => entry?.key === WORKSPACE_ITEM_META_KEY);
|
|
19
|
+
return !!meta && typeof meta === "object" && (Object.hasOwn(meta, WORKSPACE_ITEM_META_KEY) || "key" in meta && meta.key === WORKSPACE_ITEM_META_KEY);
|
|
20
|
+
}
|
|
21
|
+
if (tableName === "projects") {
|
|
22
|
+
const entries = decoded(row.data);
|
|
23
|
+
return Array.isArray(entries) && entries.some((entry) => entry && typeof entry === "object" && entry.kind === "file");
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|