or3-provider-sqlite 0.0.1

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 (32) hide show
  1. package/README.md +109 -0
  2. package/dist/module.d.mts +5 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +11 -0
  5. package/dist/runtime/server/admin/adapters/sync-sqlite.d.ts +2 -0
  6. package/dist/runtime/server/admin/adapters/sync-sqlite.js +72 -0
  7. package/dist/runtime/server/admin/stores/sqlite-store.d.ts +4 -0
  8. package/dist/runtime/server/admin/stores/sqlite-store.js +336 -0
  9. package/dist/runtime/server/auth/sqlite-auth-workspace-store.d.ts +111 -0
  10. package/dist/runtime/server/auth/sqlite-auth-workspace-store.js +349 -0
  11. package/dist/runtime/server/db/kysely.d.ts +32 -0
  12. package/dist/runtime/server/db/kysely.js +62 -0
  13. package/dist/runtime/server/db/migrate.d.ts +10 -0
  14. package/dist/runtime/server/db/migrate.js +38 -0
  15. package/dist/runtime/server/db/migrations/001_init.d.ts +6 -0
  16. package/dist/runtime/server/db/migrations/001_init.js +31 -0
  17. package/dist/runtime/server/db/migrations/002_sync_tables.d.ts +6 -0
  18. package/dist/runtime/server/db/migrations/002_sync_tables.js +55 -0
  19. package/dist/runtime/server/db/migrations/003_sync_hardening.d.ts +9 -0
  20. package/dist/runtime/server/db/migrations/003_sync_hardening.js +67 -0
  21. package/dist/runtime/server/db/migrations/004_auth_invites.d.ts +3 -0
  22. package/dist/runtime/server/db/migrations/004_auth_invites.js +18 -0
  23. package/dist/runtime/server/db/migrations/005_admin_stores.d.ts +7 -0
  24. package/dist/runtime/server/db/migrations/005_admin_stores.js +12 -0
  25. package/dist/runtime/server/db/schema.d.ts +138 -0
  26. package/dist/runtime/server/db/schema.js +10 -0
  27. package/dist/runtime/server/plugins/register.d.ts +2 -0
  28. package/dist/runtime/server/plugins/register.js +48 -0
  29. package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.d.ts +36 -0
  30. package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.js +366 -0
  31. package/dist/types.d.mts +7 -0
  32. package/package.json +54 -0
@@ -0,0 +1,67 @@
1
+ import { sql } from "kysely";
2
+ const SYNCED_TABLES = [
3
+ "s_threads",
4
+ "s_messages",
5
+ "s_projects",
6
+ "s_posts",
7
+ "s_kv",
8
+ "s_file_meta",
9
+ "s_notifications"
10
+ ];
11
+ async function rebuildSyncedTable(db, tableName) {
12
+ const nextTable = `${tableName}__new`;
13
+ await sql.raw(`DROP TABLE IF EXISTS "${nextTable}"`).execute(db);
14
+ await sql.raw(`
15
+ CREATE TABLE "${nextTable}" (
16
+ id TEXT NOT NULL,
17
+ workspace_id TEXT NOT NULL,
18
+ data_json TEXT NOT NULL,
19
+ clock INTEGER NOT NULL DEFAULT 0,
20
+ hlc TEXT NOT NULL DEFAULT '',
21
+ device_id TEXT NOT NULL DEFAULT '',
22
+ deleted INTEGER NOT NULL DEFAULT 0,
23
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
24
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
25
+ PRIMARY KEY (workspace_id, id)
26
+ )
27
+ `).execute(db);
28
+ await sql.raw(`
29
+ INSERT INTO "${nextTable}" (
30
+ id, workspace_id, data_json, clock, hlc, device_id, deleted, created_at, updated_at
31
+ )
32
+ SELECT id, workspace_id, data_json, clock, hlc, device_id, deleted, created_at, updated_at
33
+ FROM "${tableName}"
34
+ `).execute(db);
35
+ await sql.raw(`DROP TABLE "${tableName}"`).execute(db);
36
+ await sql.raw(`ALTER TABLE "${nextTable}" RENAME TO "${tableName}"`).execute(db);
37
+ await sql.raw(
38
+ `CREATE INDEX IF NOT EXISTS "idx_${tableName}_ws" ON "${tableName}" (workspace_id)`
39
+ ).execute(db);
40
+ }
41
+ export async function up(db) {
42
+ await sql.raw(`
43
+ DELETE FROM tombstones
44
+ WHERE rowid IN (
45
+ SELECT rowid
46
+ FROM (
47
+ SELECT
48
+ rowid,
49
+ ROW_NUMBER() OVER (
50
+ PARTITION BY workspace_id, table_name, pk
51
+ ORDER BY clock DESC, server_version DESC, created_at DESC, rowid DESC
52
+ ) AS rn
53
+ FROM tombstones
54
+ ) ranked
55
+ WHERE rn > 1
56
+ )
57
+ `).execute(db);
58
+ await sql.raw(`
59
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tombstones_ws_table_pk
60
+ ON tombstones(workspace_id, table_name, pk)
61
+ `).execute(db);
62
+ for (const tableName of SYNCED_TABLES) {
63
+ await rebuildSyncedTable(db, tableName);
64
+ }
65
+ }
66
+ export async function down(_db) {
67
+ }
@@ -0,0 +1,3 @@
1
+ import type { Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
@@ -0,0 +1,18 @@
1
+ import { sql } from "kysely";
2
+ export async function up(db) {
3
+ await db.schema.createTable("auth_invites").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("workspace_id", "text", (col) => col.notNull()).addColumn("email", "text", (col) => col.notNull()).addColumn("role", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("pending")).addColumn("invited_by_user_id", "text", (col) => col.notNull()).addColumn("token_hash", "text", (col) => col.notNull()).addColumn("expires_at", "integer", (col) => col.notNull()).addColumn("accepted_at", "integer").addColumn("accepted_user_id", "text").addColumn("revoked_at", "integer").addColumn(
4
+ "created_at",
5
+ "integer",
6
+ (col) => col.notNull().defaultTo(sql`(unixepoch())`)
7
+ ).addColumn(
8
+ "updated_at",
9
+ "integer",
10
+ (col) => col.notNull().defaultTo(sql`(unixepoch())`)
11
+ ).execute();
12
+ await db.schema.createIndex("idx_auth_invites_ws_status_exp").ifNotExists().on("auth_invites").columns(["workspace_id", "status", "expires_at"]).execute();
13
+ await db.schema.createIndex("idx_auth_invites_ws_email_status").ifNotExists().on("auth_invites").columns(["workspace_id", "email", "status"]).execute();
14
+ await db.schema.createIndex("idx_auth_invites_ws_token").ifNotExists().on("auth_invites").columns(["workspace_id", "token_hash"]).execute();
15
+ }
16
+ export async function down(db) {
17
+ await db.schema.dropTable("auth_invites").ifExists().execute();
18
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Migration 005: Admin store support tables.
3
+ */
4
+ import type { Kysely } from 'kysely';
5
+ import type { Or3SqliteDb } from '../schema.js';
6
+ export declare function up(db: Kysely<Or3SqliteDb>): Promise<void>;
7
+ export declare function down(db: Kysely<Or3SqliteDb>): Promise<void>;
@@ -0,0 +1,12 @@
1
+ export async function up(db) {
2
+ await db.schema.createTable("admin_users").ifNotExists().addColumn("user_id", "text", (col) => col.notNull().primaryKey()).addColumn("created_at", "integer", (col) => col.notNull()).addColumn("created_by_user_id", "text").execute();
3
+ await db.schema.createTable("admin_workspace_settings").ifNotExists().addColumn("id", "text", (col) => col.notNull().primaryKey()).addColumn("workspace_id", "text", (col) => col.notNull()).addColumn("key", "text", (col) => col.notNull()).addColumn("value", "text", (col) => col.notNull()).addColumn("updated_at", "integer", (col) => col.notNull()).execute();
4
+ await db.schema.createIndex("idx_admin_workspace_settings_workspace_key").ifNotExists().on("admin_workspace_settings").columns(["workspace_id", "key"]).unique().execute();
5
+ await db.schema.createIndex("idx_admin_workspace_settings_workspace").ifNotExists().on("admin_workspace_settings").column("workspace_id").execute();
6
+ }
7
+ export async function down(db) {
8
+ await db.schema.dropIndex("idx_admin_workspace_settings_workspace").ifExists().execute();
9
+ await db.schema.dropIndex("idx_admin_workspace_settings_workspace_key").ifExists().execute();
10
+ await db.schema.dropTable("admin_workspace_settings").ifExists().execute();
11
+ await db.schema.dropTable("admin_users").ifExists().execute();
12
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Database schema types for the SQLite provider.
3
+ * All tables use snake_case aligned with sync wire format.
4
+ */
5
+ import type { Generated, Insertable, Selectable } from 'kysely';
6
+ export interface UsersTable {
7
+ id: string;
8
+ email: string | null;
9
+ display_name: string | null;
10
+ active_workspace_id: string | null;
11
+ created_at: Generated<number>;
12
+ }
13
+ export interface AuthAccountsTable {
14
+ id: string;
15
+ user_id: string;
16
+ provider: string;
17
+ provider_user_id: string;
18
+ created_at: Generated<number>;
19
+ }
20
+ export interface WorkspacesTable {
21
+ id: string;
22
+ name: string;
23
+ description: string | null;
24
+ owner_user_id: string;
25
+ created_at: Generated<number>;
26
+ deleted: Generated<number>;
27
+ deleted_at: number | null;
28
+ }
29
+ export interface WorkspaceMembersTable {
30
+ id: string;
31
+ workspace_id: string;
32
+ user_id: string;
33
+ role: string;
34
+ created_at: Generated<number>;
35
+ }
36
+ export interface AuthInvitesTable {
37
+ id: string;
38
+ workspace_id: string;
39
+ email: string;
40
+ role: string;
41
+ status: string;
42
+ invited_by_user_id: string;
43
+ token_hash: string;
44
+ expires_at: number;
45
+ accepted_at: number | null;
46
+ accepted_user_id: string | null;
47
+ revoked_at: number | null;
48
+ created_at: Generated<number>;
49
+ updated_at: Generated<number>;
50
+ }
51
+ export interface ServerVersionCounterTable {
52
+ workspace_id: string;
53
+ value: Generated<number>;
54
+ }
55
+ export interface ChangeLogTable {
56
+ id: string;
57
+ workspace_id: string;
58
+ server_version: number;
59
+ table_name: string;
60
+ pk: string;
61
+ op: string;
62
+ payload_json: string | null;
63
+ clock: number;
64
+ hlc: string;
65
+ device_id: string;
66
+ op_id: string;
67
+ created_at: Generated<number>;
68
+ }
69
+ export interface DeviceCursorsTable {
70
+ id: string;
71
+ workspace_id: string;
72
+ device_id: string;
73
+ last_seen_version: number;
74
+ updated_at: Generated<number>;
75
+ }
76
+ export interface TombstonesTable {
77
+ id: string;
78
+ workspace_id: string;
79
+ table_name: string;
80
+ pk: string;
81
+ deleted_at: number;
82
+ clock: number;
83
+ server_version: number;
84
+ created_at: Generated<number>;
85
+ }
86
+ export interface SyncedEntityTable {
87
+ id: string;
88
+ workspace_id: string;
89
+ data_json: string;
90
+ clock: number;
91
+ hlc: string;
92
+ device_id: string;
93
+ deleted: Generated<number>;
94
+ created_at: Generated<number>;
95
+ updated_at: Generated<number>;
96
+ }
97
+ export interface AdminUsersTable {
98
+ user_id: string;
99
+ created_at: number;
100
+ created_by_user_id: string | null;
101
+ }
102
+ export interface AdminWorkspaceSettingsTable {
103
+ id: string;
104
+ workspace_id: string;
105
+ key: string;
106
+ value: string;
107
+ updated_at: number;
108
+ }
109
+ export interface Or3SqliteDb {
110
+ users: UsersTable;
111
+ auth_accounts: AuthAccountsTable;
112
+ workspaces: WorkspacesTable;
113
+ workspace_members: WorkspaceMembersTable;
114
+ auth_invites: AuthInvitesTable;
115
+ server_version_counter: ServerVersionCounterTable;
116
+ change_log: ChangeLogTable;
117
+ device_cursors: DeviceCursorsTable;
118
+ tombstones: TombstonesTable;
119
+ s_threads: SyncedEntityTable;
120
+ s_messages: SyncedEntityTable;
121
+ s_projects: SyncedEntityTable;
122
+ s_posts: SyncedEntityTable;
123
+ s_kv: SyncedEntityTable;
124
+ s_file_meta: SyncedEntityTable;
125
+ s_notifications: SyncedEntityTable;
126
+ admin_users: AdminUsersTable;
127
+ admin_workspace_settings: AdminWorkspaceSettingsTable;
128
+ }
129
+ export type User = Selectable<UsersTable>;
130
+ export type NewUser = Insertable<UsersTable>;
131
+ export type AuthAccount = Selectable<AuthAccountsTable>;
132
+ export type Workspace = Selectable<WorkspacesTable>;
133
+ export type WorkspaceMember = Selectable<WorkspaceMembersTable>;
134
+ export type ChangeLogRow = Selectable<ChangeLogTable>;
135
+ /** Map sync table name -> materialized table name */
136
+ export declare const SYNCED_TABLE_MAP: Record<string, keyof Or3SqliteDb>;
137
+ /** List of allowed sync table names */
138
+ export declare const ALLOWED_SYNC_TABLES: string[];
@@ -0,0 +1,10 @@
1
+ export const SYNCED_TABLE_MAP = {
2
+ threads: "s_threads",
3
+ messages: "s_messages",
4
+ projects: "s_projects",
5
+ posts: "s_posts",
6
+ kv: "s_kv",
7
+ file_meta: "s_file_meta",
8
+ notifications: "s_notifications"
9
+ };
10
+ export const ALLOWED_SYNC_TABLES = Object.keys(SYNCED_TABLE_MAP);
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,48 @@
1
+ import { registerAuthWorkspaceStore } from "~~/server/auth/store/registry";
2
+ import { registerProviderAdminAdapter } from "~~/server/admin/providers/registry";
3
+ import { registerAdminStoreProvider } from "~~/server/admin/stores/registry";
4
+ import { registerSyncGatewayAdapter } from "~~/server/sync/gateway/registry";
5
+ import { createSqliteAuthWorkspaceStore } from "../auth/sqlite-auth-workspace-store.js";
6
+ import { createSqliteSyncGatewayAdapter } from "../sync/sqlite-sync-gateway-adapter.js";
7
+ import {
8
+ createSqliteAdminUserStore,
9
+ createSqliteWorkspaceAccessStore,
10
+ createSqliteWorkspaceSettingsStore
11
+ } from "../admin/stores/sqlite-store.js";
12
+ import { sqliteSyncAdminAdapter } from "../admin/adapters/sync-sqlite.js";
13
+ import { getSqliteDb } from "../db/kysely.js";
14
+ import { runMigrations } from "../db/migrate.js";
15
+ import { useRuntimeConfig } from "#imports";
16
+ const SQLITE_PROVIDER_ID = "sqlite";
17
+ export default defineNitroPlugin(async () => {
18
+ const config = useRuntimeConfig();
19
+ if (!config.auth?.enabled) return;
20
+ if (!config.sync?.enabled) return;
21
+ if (config.sync?.provider !== SQLITE_PROVIDER_ID) return;
22
+ const db = getSqliteDb();
23
+ await runMigrations(db);
24
+ registerAuthWorkspaceStore({
25
+ id: SQLITE_PROVIDER_ID,
26
+ order: 100,
27
+ create: createSqliteAuthWorkspaceStore
28
+ });
29
+ registerSyncGatewayAdapter({
30
+ id: SQLITE_PROVIDER_ID,
31
+ order: 100,
32
+ create: createSqliteSyncGatewayAdapter
33
+ });
34
+ registerAdminStoreProvider({
35
+ id: SQLITE_PROVIDER_ID,
36
+ createWorkspaceAccessStore: createSqliteWorkspaceAccessStore,
37
+ createWorkspaceSettingsStore: createSqliteWorkspaceSettingsStore,
38
+ createAdminUserStore: createSqliteAdminUserStore,
39
+ getCapabilities: () => ({
40
+ supportsServerSideAdmin: true,
41
+ supportsUserSearch: true,
42
+ supportsWorkspaceList: true,
43
+ supportsWorkspaceManagement: true,
44
+ supportsDeploymentAdminGrants: true
45
+ })
46
+ });
47
+ registerProviderAdminAdapter(sqliteSyncAdminAdapter);
48
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * SQLite implementation of SyncGatewayAdapter.
3
+ *
4
+ * Handles push/pull/updateCursor/gc using Kysely + better-sqlite3.
5
+ * Uses raw DB transactions via better-sqlite3 for push atomicity
6
+ * (BEGIN IMMEDIATE prevents concurrent server_version races).
7
+ */
8
+ import type { H3Event } from 'h3';
9
+ import type { SyncGatewayAdapter } from '~~/server/sync/gateway/types';
10
+ import type { PullRequest, PullResponse, PushBatch, PushResult } from '~~/shared/sync/types';
11
+ export declare class SqliteSyncGatewayAdapter implements SyncGatewayAdapter {
12
+ id: string;
13
+ private get db();
14
+ push(event: H3Event, input: PushBatch): Promise<PushResult>;
15
+ pull(event: H3Event, input: PullRequest): Promise<PullResponse>;
16
+ updateCursor(event: H3Event, input: {
17
+ scope: {
18
+ workspaceId: string;
19
+ };
20
+ deviceId: string;
21
+ version: number;
22
+ }): Promise<void>;
23
+ gcTombstones(event: H3Event, input: {
24
+ scope: {
25
+ workspaceId: string;
26
+ };
27
+ retentionSeconds: number;
28
+ }): Promise<void>;
29
+ gcChangeLog(event: H3Event, input: {
30
+ scope: {
31
+ workspaceId: string;
32
+ };
33
+ retentionSeconds: number;
34
+ }): Promise<void>;
35
+ }
36
+ export declare function createSqliteSyncGatewayAdapter(): SyncGatewayAdapter;