@zveltio/sdk 2.0.8 → 3.0.0-beta.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.
- package/dist/build/index.d.ts +77 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +157 -0
- package/dist/client/QueryBuilder.d.ts.map +1 -1
- package/dist/client/QueryBuilder.js +12 -3
- package/dist/client/RealtimeClient.d.ts.map +1 -1
- package/dist/client/RealtimeClient.js +3 -1
- package/dist/client/ZveltioClient.d.ts.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -5
- package/dist/core.d.ts.map +1 -1
- package/dist/ddl/index.d.ts +153 -0
- package/dist/ddl/index.d.ts.map +1 -0
- package/dist/ddl/index.js +33 -0
- package/dist/extension/index.d.ts +522 -10
- package/dist/extension/index.d.ts.map +1 -1
- package/dist/extension/index.js +1 -1
- package/dist/extension/permission-gate.d.ts +37 -0
- package/dist/extension/permission-gate.d.ts.map +1 -0
- package/dist/extension/permission-gate.js +93 -0
- package/dist/generate-types.d.ts +37 -0
- package/dist/generate-types.d.ts.map +1 -0
- package/dist/generate-types.js +157 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/local-store.d.ts.map +1 -1
- package/dist/local-store.js +1 -3
- package/dist/offline/index.d.ts +105 -0
- package/dist/offline/index.d.ts.map +1 -0
- package/dist/offline/index.js +235 -0
- package/dist/publish/index.d.ts +94 -0
- package/dist/publish/index.d.ts.map +1 -0
- package/dist/publish/index.js +148 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +6 -2
- package/dist/rpc/index.d.ts +68 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/rpc/index.js +61 -0
- package/dist/schema-codegen.d.ts +60 -0
- package/dist/schema-codegen.d.ts.map +1 -0
- package/dist/schema-codegen.js +369 -0
- package/dist/schema-watcher.d.ts.map +1 -1
- package/dist/schema-watcher.js +1 -3
- package/dist/studio/index.d.ts +84 -0
- package/dist/studio/index.d.ts.map +1 -0
- package/dist/studio/index.js +180 -0
- package/dist/svelte.d.ts.map +1 -1
- package/dist/sync-manager.d.ts.map +1 -1
- package/dist/sync-manager.js +4 -11
- package/dist/testing/index.d.ts +121 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +283 -0
- package/dist/testing/with-test-db.d.ts +119 -0
- package/dist/testing/with-test-db.d.ts.map +1 -0
- package/dist/testing/with-test-db.js +313 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -1
- package/dist/validate/index.d.ts +95 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +201 -0
- package/dist/validate/migration-parse.d.ts +17 -0
- package/dist/validate/migration-parse.d.ts.map +1 -0
- package/dist/validate/migration-parse.js +11 -0
- package/package.json +53 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `permissionGate` — minimal route-level RBAC for extensions.
|
|
3
|
+
*
|
|
4
|
+
* Default-deny: if no Casbin policy grants the resource/action, the request
|
|
5
|
+
* is rejected with 403. The god role bypasses the gate (handled by the
|
|
6
|
+
* engine's `checkPermission`).
|
|
7
|
+
*
|
|
8
|
+
* Action is derived from the HTTP method using the standard CRUD mapping
|
|
9
|
+
* (GET → read, POST → create, PATCH/PUT → update, DELETE → delete). The
|
|
10
|
+
* `resource` is the extension's logical name (e.g. `'crm'`, `'invoices'`);
|
|
11
|
+
* operators grant access by adding a Casbin policy:
|
|
12
|
+
*
|
|
13
|
+
* INSERT INTO zvd_permissions (ptype, v0, v1, v2)
|
|
14
|
+
* VALUES ('p', 'user_role', 'crm', 'read');
|
|
15
|
+
*
|
|
16
|
+
* Why this exists: many extensions historically only checked
|
|
17
|
+
* `auth.api.getSession(...)` — i.e. any authenticated user could
|
|
18
|
+
* read/write the entire extension. That's the right default for a
|
|
19
|
+
* single-tenant deployment but unacceptable in multi-tenant or
|
|
20
|
+
* role-segmented installs. The gate gives operators a single knob to
|
|
21
|
+
* tighten access without each extension having to roll its own RBAC.
|
|
22
|
+
*
|
|
23
|
+
* Usage in an extension's `routes.ts`:
|
|
24
|
+
*
|
|
25
|
+
* app.use('*', permissionGate(ctx, 'crm'));
|
|
26
|
+
*
|
|
27
|
+
* Place AFTER the auth-guard middleware that sets `c.set('user', ...)`.
|
|
28
|
+
*/
|
|
29
|
+
function methodToAction(method) {
|
|
30
|
+
const m = method.toUpperCase();
|
|
31
|
+
switch (m) {
|
|
32
|
+
case 'GET':
|
|
33
|
+
case 'HEAD':
|
|
34
|
+
case 'OPTIONS':
|
|
35
|
+
return 'read';
|
|
36
|
+
case 'POST':
|
|
37
|
+
return 'create';
|
|
38
|
+
case 'PATCH':
|
|
39
|
+
case 'PUT':
|
|
40
|
+
return 'update';
|
|
41
|
+
case 'DELETE':
|
|
42
|
+
return 'delete';
|
|
43
|
+
default:
|
|
44
|
+
// Unknown method → demand the most restrictive action so the gate
|
|
45
|
+
// never fails open.
|
|
46
|
+
return 'delete';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Operators can disable the gate temporarily during a permission backfill
|
|
51
|
+
* by setting `EXTENSION_RBAC=permissive`. When permissive, the gate becomes
|
|
52
|
+
* a no-op (any authenticated user passes) but still logs the denied
|
|
53
|
+
* attempts so the operator can preview what *would* have been blocked
|
|
54
|
+
* before flipping to strict.
|
|
55
|
+
*
|
|
56
|
+
* Strict (default) is the secure posture and what the SDK ships with.
|
|
57
|
+
*/
|
|
58
|
+
function rbacMode() {
|
|
59
|
+
const v = globalThis.process?.env?.EXTENSION_RBAC;
|
|
60
|
+
return v === 'permissive' ? 'permissive' : 'strict';
|
|
61
|
+
}
|
|
62
|
+
export function permissionGate(ctx, resource, opts = {}) {
|
|
63
|
+
return async (c, next) => {
|
|
64
|
+
// CORS preflight (OPTIONS) must never be authorization-gated —
|
|
65
|
+
// browsers send it without credentials and a 401/403 here breaks
|
|
66
|
+
// the actual cross-origin request that would follow. The global
|
|
67
|
+
// CORS middleware in the engine returns the preflight response;
|
|
68
|
+
// we just need to let it through.
|
|
69
|
+
if (c.req.method === 'OPTIONS') {
|
|
70
|
+
await next();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const user = c.get('user');
|
|
74
|
+
if (!user?.id) {
|
|
75
|
+
return c.json({ error: 'Unauthorized' }, 401);
|
|
76
|
+
}
|
|
77
|
+
const methodKey = c.req.method.toUpperCase();
|
|
78
|
+
const action = opts.actionOverrides?.[methodKey] ?? methodToAction(methodKey);
|
|
79
|
+
const allowed = await ctx.checkPermission(user.id, resource, action);
|
|
80
|
+
if (!allowed) {
|
|
81
|
+
if (rbacMode() === 'permissive') {
|
|
82
|
+
// Audit-log mode: surface what WOULD be blocked so operators can
|
|
83
|
+
// configure policies before flipping to strict. Stays a single
|
|
84
|
+
// console.warn — no DB writes on the hot path.
|
|
85
|
+
console.warn(`[permissionGate] WOULD DENY user=${user.id} ${c.req.method} ${c.req.path} → ${resource}:${action} (EXTENSION_RBAC=permissive — gate bypassed)`);
|
|
86
|
+
await next();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
return c.json({ error: `Forbidden: missing ${resource}:${action} permission` }, 403);
|
|
90
|
+
}
|
|
91
|
+
await next();
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generate-types.ts
|
|
3
|
+
*
|
|
4
|
+
* Programmatic type generator for Zveltio collections.
|
|
5
|
+
* Fetches collection schemas from a running Zveltio engine and generates
|
|
6
|
+
* TypeScript interface declarations with full type information.
|
|
7
|
+
*
|
|
8
|
+
* Usage (programmatic):
|
|
9
|
+
* import { generateTypes } from '@zveltio/sdk/generate-types';
|
|
10
|
+
* const dts = await generateTypes({ url: 'http://localhost:3000', apiKey: 'sk-...' });
|
|
11
|
+
* await fs.writeFile('./types/zveltio.d.ts', dts);
|
|
12
|
+
*
|
|
13
|
+
* Usage (CLI):
|
|
14
|
+
* zveltio generate-types --output ./types/zveltio.d.ts
|
|
15
|
+
* zveltio types --output ./types/zveltio.d.ts
|
|
16
|
+
*/
|
|
17
|
+
export interface GenerateTypesOptions {
|
|
18
|
+
/** Zveltio engine URL (default: http://localhost:3000) */
|
|
19
|
+
url?: string;
|
|
20
|
+
/** API key for authentication */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
/** Only generate types for this collection */
|
|
23
|
+
collection?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generates TypeScript declarations for all (or one) Zveltio collection(s).
|
|
27
|
+
*
|
|
28
|
+
* Returns the `.d.ts` file content as a string. The caller is responsible for
|
|
29
|
+
* writing it to disk.
|
|
30
|
+
*
|
|
31
|
+
* The engine endpoint `/api/admin/types` is used when available (returns
|
|
32
|
+
* pre-generated TS from the server's field-type registry). If the endpoint
|
|
33
|
+
* fails or is unavailable this function falls back to generating types from
|
|
34
|
+
* the collection schema returned by `/api/collections`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateTypes(options?: GenerateTypesOptions): Promise<string>;
|
|
37
|
+
//# sourceMappingURL=generate-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-types.d.ts","sourceRoot":"","sources":["../src/generate-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA0HH,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyDvF"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generate-types.ts
|
|
3
|
+
*
|
|
4
|
+
* Programmatic type generator for Zveltio collections.
|
|
5
|
+
* Fetches collection schemas from a running Zveltio engine and generates
|
|
6
|
+
* TypeScript interface declarations with full type information.
|
|
7
|
+
*
|
|
8
|
+
* Usage (programmatic):
|
|
9
|
+
* import { generateTypes } from '@zveltio/sdk/generate-types';
|
|
10
|
+
* const dts = await generateTypes({ url: 'http://localhost:3000', apiKey: 'sk-...' });
|
|
11
|
+
* await fs.writeFile('./types/zveltio.d.ts', dts);
|
|
12
|
+
*
|
|
13
|
+
* Usage (CLI):
|
|
14
|
+
* zveltio generate-types --output ./types/zveltio.d.ts
|
|
15
|
+
* zveltio types --output ./types/zveltio.d.ts
|
|
16
|
+
*/
|
|
17
|
+
// ── Field type → TypeScript type mapping ─────────────────────────────────────
|
|
18
|
+
const FIELD_TYPE_MAP = {
|
|
19
|
+
text: 'string',
|
|
20
|
+
textarea: 'string',
|
|
21
|
+
richtext: 'string',
|
|
22
|
+
markdown: 'string',
|
|
23
|
+
email: 'string',
|
|
24
|
+
url: 'string',
|
|
25
|
+
password: 'string',
|
|
26
|
+
slug: 'string',
|
|
27
|
+
uuid: 'string',
|
|
28
|
+
color: 'string',
|
|
29
|
+
number: 'number',
|
|
30
|
+
integer: 'number',
|
|
31
|
+
decimal: 'number',
|
|
32
|
+
float: 'number',
|
|
33
|
+
currency: 'number',
|
|
34
|
+
percentage: 'number',
|
|
35
|
+
boolean: 'boolean',
|
|
36
|
+
toggle: 'boolean',
|
|
37
|
+
date: 'string', // ISO 8601 date string
|
|
38
|
+
datetime: 'string', // ISO 8601 datetime string
|
|
39
|
+
time: 'string',
|
|
40
|
+
timestamp: 'string',
|
|
41
|
+
json: 'Record<string, unknown>',
|
|
42
|
+
jsonb: 'Record<string, unknown>',
|
|
43
|
+
object: 'Record<string, unknown>',
|
|
44
|
+
array: 'unknown[]',
|
|
45
|
+
tags: 'string[]',
|
|
46
|
+
select: 'string',
|
|
47
|
+
multiselect: 'string[]',
|
|
48
|
+
relation: 'string', // UUID of related record
|
|
49
|
+
file: 'string', // storage path
|
|
50
|
+
image: 'string', // storage path
|
|
51
|
+
point: '{ lat: number; lng: number }',
|
|
52
|
+
geojson: 'GeoJSON.Geometry',
|
|
53
|
+
};
|
|
54
|
+
function fieldTypeToTs(fieldType) {
|
|
55
|
+
return FIELD_TYPE_MAP[fieldType.toLowerCase()] ?? 'unknown';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Converts a collection name (snake_case) to a PascalCase interface name.
|
|
59
|
+
* Examples: "my_orders" → "MyOrders", "products" → "Products"
|
|
60
|
+
*/
|
|
61
|
+
function toPascalCase(name) {
|
|
62
|
+
return name
|
|
63
|
+
.split(/[_\s-]+/)
|
|
64
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
65
|
+
.join('');
|
|
66
|
+
}
|
|
67
|
+
function generateCollectionInterface(col) {
|
|
68
|
+
const interfaceName = toPascalCase(col.name) + 'Collection';
|
|
69
|
+
const fields = col.fields ?? [];
|
|
70
|
+
const lines = [];
|
|
71
|
+
if (col.description) {
|
|
72
|
+
lines.push(`/** ${col.description} */`);
|
|
73
|
+
}
|
|
74
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
75
|
+
lines.push(` /** Internal record ID (UUID) */`);
|
|
76
|
+
lines.push(` id: string;`);
|
|
77
|
+
for (const field of fields) {
|
|
78
|
+
if (field.description) {
|
|
79
|
+
lines.push(` /** ${field.description} */`);
|
|
80
|
+
}
|
|
81
|
+
const tsType = fieldTypeToTs(field.type);
|
|
82
|
+
const optional = field.required ? '' : '?';
|
|
83
|
+
lines.push(` ${field.name}${optional}: ${tsType};`);
|
|
84
|
+
}
|
|
85
|
+
lines.push(` /** ISO 8601 creation timestamp */`);
|
|
86
|
+
lines.push(` created_at: string;`);
|
|
87
|
+
lines.push(` /** ISO 8601 last-updated timestamp */`);
|
|
88
|
+
lines.push(` updated_at: string;`);
|
|
89
|
+
lines.push(`}`);
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
function generateIndexInterface(collections) {
|
|
93
|
+
const lines = [];
|
|
94
|
+
lines.push(`/** Index of all Zveltio collections — use with \`ZveltioClient<ZveltioCollections>\` */`);
|
|
95
|
+
lines.push(`export interface ZveltioCollections {`);
|
|
96
|
+
for (const col of collections) {
|
|
97
|
+
const interfaceName = toPascalCase(col.name) + 'Collection';
|
|
98
|
+
const comment = col.display_name || col.name;
|
|
99
|
+
lines.push(` /** ${comment} */`);
|
|
100
|
+
lines.push(` ${col.name}: ${interfaceName};`);
|
|
101
|
+
}
|
|
102
|
+
lines.push(`}`);
|
|
103
|
+
return lines.join('\n');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Generates TypeScript declarations for all (or one) Zveltio collection(s).
|
|
107
|
+
*
|
|
108
|
+
* Returns the `.d.ts` file content as a string. The caller is responsible for
|
|
109
|
+
* writing it to disk.
|
|
110
|
+
*
|
|
111
|
+
* The engine endpoint `/api/admin/types` is used when available (returns
|
|
112
|
+
* pre-generated TS from the server's field-type registry). If the endpoint
|
|
113
|
+
* fails or is unavailable this function falls back to generating types from
|
|
114
|
+
* the collection schema returned by `/api/collections`.
|
|
115
|
+
*/
|
|
116
|
+
export async function generateTypes(options = {}) {
|
|
117
|
+
const engineUrl = (options.url ?? process.env.ZVELTIO_URL ?? 'http://localhost:3000').replace(/\/$/, '');
|
|
118
|
+
const apiKey = options.apiKey ?? process.env.ZVELTIO_API_KEY ?? '';
|
|
119
|
+
const headers = {};
|
|
120
|
+
if (apiKey)
|
|
121
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
122
|
+
// ── Try fast path: engine pre-generates types ────────────────────────────
|
|
123
|
+
try {
|
|
124
|
+
const path = options.collection
|
|
125
|
+
? `/api/admin/types/${encodeURIComponent(options.collection)}`
|
|
126
|
+
: '/api/admin/types';
|
|
127
|
+
const res = await fetch(`${engineUrl}${path}`, { headers });
|
|
128
|
+
if (res.ok) {
|
|
129
|
+
return await res.text();
|
|
130
|
+
}
|
|
131
|
+
// Fall through to manual generation on 404/401/500
|
|
132
|
+
if (res.status !== 404) {
|
|
133
|
+
console.warn(`[generate-types] Engine /api/admin/types returned ${res.status}, falling back to manual generation.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Engine unreachable or endpoint absent — use fallback
|
|
138
|
+
}
|
|
139
|
+
// ── Fallback: fetch schema and generate types locally ────────────────────
|
|
140
|
+
const collectionsUrl = options.collection
|
|
141
|
+
? `${engineUrl}/api/collections/${encodeURIComponent(options.collection)}`
|
|
142
|
+
: `${engineUrl}/api/collections`;
|
|
143
|
+
const res = await fetch(collectionsUrl, { headers });
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
throw new Error(`Failed to fetch collections from ${collectionsUrl}: ${res.status} ${res.statusText}`);
|
|
146
|
+
}
|
|
147
|
+
const data = (await res.json());
|
|
148
|
+
const collections = Array.isArray(data.collections)
|
|
149
|
+
? data.collections.filter((c) => !c.is_system)
|
|
150
|
+
: data.collection
|
|
151
|
+
? [data.collection]
|
|
152
|
+
: [];
|
|
153
|
+
const header = `// Auto-generated by Zveltio\n// Do not edit manually — run: zveltio generate-types\n// Generated: ${new Date().toISOString()}\n`;
|
|
154
|
+
const interfaces = collections.map(generateCollectionInterface).join('\n\n');
|
|
155
|
+
const indexType = options.collection ? '' : '\n\n' + generateIndexInterface(collections);
|
|
156
|
+
return `${header}\n${interfaces}${indexType}\n`;
|
|
157
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { ZveltioClientConfig, ListParams, ListResult } from './client.js';
|
|
|
3
3
|
export { ZveltioRealtime } from './realtime.js';
|
|
4
4
|
export { LocalStore } from './local-store.js';
|
|
5
5
|
export { SyncManager } from './sync-manager.js';
|
|
6
|
-
export type { ZveltioExtension } from './extension/index.js';
|
|
6
|
+
export type { ZveltioExtension, ExtensionContext, FieldTypeRegistryAPI, StudioExtensionAPI, StudioRoute, StudioFieldType, AssetPreviewHandler, } from './extension/index.js';
|
|
7
7
|
export type { LocalRecord, SyncQueueItem } from './local-store.js';
|
|
8
8
|
export type { SyncManagerConfig } from './sync-manager.js';
|
|
9
9
|
export { useSyncCollection, useSyncStatus } from './svelte.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,mBAAmB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,mBAAmB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACnE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC/D,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAG1B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-store.d.ts","sourceRoot":"","sources":["../src/local-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAoD,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAG/F,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACpC,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAA6B;IACvC,OAAO,CAAC,KAAK,CAA6B;IAEpC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAuCb,cAAc;IAK5B,iDAAiD;IAC3C,GAAG,
|
|
1
|
+
{"version":3,"file":"local-store.d.ts","sourceRoot":"","sources":["../src/local-store.ts"],"names":[],"mappings":"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"}
|
package/dist/local-store.js
CHANGED
|
@@ -134,9 +134,7 @@ export class LocalStore {
|
|
|
134
134
|
// Remove from queue
|
|
135
135
|
await tx.objectStore('sync_queue').delete(queueItemId);
|
|
136
136
|
// Update record status
|
|
137
|
-
const record = (await tx
|
|
138
|
-
.objectStore('records')
|
|
139
|
-
.get([collection, recordId]));
|
|
137
|
+
const record = (await tx.objectStore('records').get([collection, recordId]));
|
|
140
138
|
if (record) {
|
|
141
139
|
record._serverVersion = serverVersion;
|
|
142
140
|
record._syncStatus = 'synced';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline-sync provider abstraction.
|
|
3
|
+
*
|
|
4
|
+
* The SDK ships TWO sync strategies for the Postgres ↔ client-SQLite
|
|
5
|
+
* loop, picked at construction time:
|
|
6
|
+
*
|
|
7
|
+
* - **`crdt`** (default) — bespoke field-level Last-Write-Wins merge on
|
|
8
|
+
* top of IndexedDB / SQLite via `LocalStore` + `SyncManager`. Works
|
|
9
|
+
* offline, applies on reconnect, handles conflicts. No external
|
|
10
|
+
* dependency beyond the SDK + engine.
|
|
11
|
+
*
|
|
12
|
+
* - **`electric`** — Electric SQL replication. Postgres logical-
|
|
13
|
+
* replication slot → Electric service → client. Conflicts are
|
|
14
|
+
* resolved server-side via CRDTs Electric implements internally.
|
|
15
|
+
* Requires an Electric service running alongside Postgres + the
|
|
16
|
+
* engine routes from `routes/electric.ts` that mint short-lived
|
|
17
|
+
* JWTs the client uses to authenticate.
|
|
18
|
+
*
|
|
19
|
+
* Auth flow for the electric provider
|
|
20
|
+
* -----------------------------------
|
|
21
|
+
* 1. Provider calls `POST {engineUrl}/api/electric/auth` (with
|
|
22
|
+
* credentials; the engine reads the better-auth session).
|
|
23
|
+
* 2. Engine returns `{ token, expiresAt, electricUrl }`.
|
|
24
|
+
* 3. Provider opens a websocket to `${electricUrl}?token=<jwt>`.
|
|
25
|
+
* 4. Before each token expires, the provider re-requests a fresh one
|
|
26
|
+
* (no user interaction).
|
|
27
|
+
*
|
|
28
|
+
* Pull/push semantics
|
|
29
|
+
* -------------------
|
|
30
|
+
* Electric replicates continuously over the websocket — there's no
|
|
31
|
+
* explicit "pull" call. The provider's `pull()` is a no-op that returns
|
|
32
|
+
* once the initial sync handshake completes. `push()` is the same: local
|
|
33
|
+
* writes go through Electric's normal replication path automatically.
|
|
34
|
+
* Both shims exist so the OfflineProvider interface stays uniform across
|
|
35
|
+
* crdt/electric.
|
|
36
|
+
*
|
|
37
|
+
* Subscriptions
|
|
38
|
+
* -------------
|
|
39
|
+
* `subscribe('zvd_contacts', cb)` registers a callback fired whenever
|
|
40
|
+
* Electric pushes a change for that table. The implementation uses the
|
|
41
|
+
* websocket's `message` event with a routing table; one socket, many
|
|
42
|
+
* subscriptions.
|
|
43
|
+
*/
|
|
44
|
+
export type OfflineProviderKind = 'crdt' | 'electric';
|
|
45
|
+
export interface OfflineProviderConfig {
|
|
46
|
+
/** Which sync engine to use. Default: 'crdt'. */
|
|
47
|
+
provider?: OfflineProviderKind;
|
|
48
|
+
/** Engine base URL — used for the token-mint call AND CRDT push/pull. */
|
|
49
|
+
engineUrl: string;
|
|
50
|
+
/**
|
|
51
|
+
* Override the Electric websocket URL. When omitted, the provider asks
|
|
52
|
+
* the engine via `GET /api/electric/config` so operators have a single
|
|
53
|
+
* place to configure it. Useful for tests + dev setups.
|
|
54
|
+
*/
|
|
55
|
+
electricUrl?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Tables to record on the audit token claim. Does NOT restrict what
|
|
58
|
+
* Electric replicates — Electric enforces that via its own config.
|
|
59
|
+
*/
|
|
60
|
+
tables?: string[];
|
|
61
|
+
/**
|
|
62
|
+
* Override the `fetch` impl — handy for tests + SSR. Default: globalThis.fetch.
|
|
63
|
+
*/
|
|
64
|
+
fetch?: typeof fetch;
|
|
65
|
+
/**
|
|
66
|
+
* Override the WebSocket constructor — handy for tests + non-browser
|
|
67
|
+
* runtimes. Default: globalThis.WebSocket.
|
|
68
|
+
*/
|
|
69
|
+
websocket?: typeof WebSocket;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Public interface every provider implements. Today the CRDT provider
|
|
73
|
+
* wraps `SyncManager`; the Electric provider wraps a websocket. Both
|
|
74
|
+
* expose the same surface.
|
|
75
|
+
*/
|
|
76
|
+
export interface OfflineProvider {
|
|
77
|
+
readonly kind: OfflineProviderKind;
|
|
78
|
+
/** Pull remote rows into the local store. Cheap to call repeatedly. */
|
|
79
|
+
pull(): Promise<void>;
|
|
80
|
+
/** Push local mutations to the server. Returns the number of ops sent. */
|
|
81
|
+
push(): Promise<number>;
|
|
82
|
+
/** Subscribe to live changes on a table. Returns an unsubscribe fn. */
|
|
83
|
+
subscribe(table: string, cb: (rows: unknown[]) => void): () => void;
|
|
84
|
+
/** Stop background sync + release resources. */
|
|
85
|
+
close(): Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
export declare class ElectricNotConfigured extends Error {
|
|
88
|
+
constructor(reason: string);
|
|
89
|
+
}
|
|
90
|
+
export declare class ElectricUnavailable extends Error {
|
|
91
|
+
constructor(reason: string);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build the configured offline-sync provider.
|
|
95
|
+
*
|
|
96
|
+
* For `crdt` (default): returns a thin adapter around `SyncManager`.
|
|
97
|
+
*
|
|
98
|
+
* For `electric`: returns the real Electric provider. Throws
|
|
99
|
+
* `ElectricNotConfigured` synchronously when required config is missing
|
|
100
|
+
* (no electricUrl AND no way to discover one), or `ElectricUnavailable`
|
|
101
|
+
* lazily on the first network op when the engine reports Electric is
|
|
102
|
+
* disabled.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createOfflineProvider(config: OfflineProviderConfig): Promise<OfflineProvider>;
|
|
105
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/offline/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,UAAU,CAAC;AAEtD,MAAM,WAAW,qBAAqB;IACpC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,uEAAuE;IACvE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,0EAA0E;IAC1E,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,uEAAuE;IACvE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACpE,gDAAgD;IAChD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,MAAM,EAAE,MAAM;CAQ3B;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,MAAM,EAAE,MAAM;CAQ3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,eAAe,CAAC,CAQ1B"}
|