@zveltio/sdk 2.0.8 → 3.0.0-beta.2
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,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-publish validators for Zveltio extensions (S4-04).
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no Node-specific imports, no Zod runtime. Designed so the
|
|
5
|
+
* CLI (`zveltio extension validate`) can call them on file contents read from
|
|
6
|
+
* disk, the engine can call them on uploaded archives, and registry code can
|
|
7
|
+
* call them on publish. Each returns a `ValidationResult` with structured
|
|
8
|
+
* diagnostics instead of throwing, so the CLI can print all problems at once.
|
|
9
|
+
*
|
|
10
|
+
* What's checked:
|
|
11
|
+
* - manifest.json shape (required fields, semver, sane category)
|
|
12
|
+
* - name matches the folder path it lives in
|
|
13
|
+
* - peerDependencies appear on the platform allow-list
|
|
14
|
+
* - migration SQL files parse + table coverage looks sane
|
|
15
|
+
* - destructive DDL in migrations has a `-- DOWN` section
|
|
16
|
+
* - bundle size stays within quota
|
|
17
|
+
*
|
|
18
|
+
* Out of scope (deferred):
|
|
19
|
+
* - Loading the extension module to inspect its default export. Requires
|
|
20
|
+
* a Bun runtime + sandbox. CLI's `validate` cannot guarantee correctness
|
|
21
|
+
* for runtime contracts; that's what `extension types` (S4-01) + `tsc`
|
|
22
|
+
* gives you locally.
|
|
23
|
+
* - Building the Studio bundle. Too slow; pair with `zveltio extension build`
|
|
24
|
+
* which can call validate as a sub-step.
|
|
25
|
+
*/
|
|
26
|
+
import { parseMigrationSql } from './migration-parse.js';
|
|
27
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
28
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/;
|
|
29
|
+
const KNOWN_CATEGORIES = new Set([
|
|
30
|
+
'auth',
|
|
31
|
+
'content',
|
|
32
|
+
'crm',
|
|
33
|
+
'finance',
|
|
34
|
+
'hr',
|
|
35
|
+
'operations',
|
|
36
|
+
'developer',
|
|
37
|
+
'compliance',
|
|
38
|
+
'communications',
|
|
39
|
+
'analytics',
|
|
40
|
+
'geospatial',
|
|
41
|
+
'ai',
|
|
42
|
+
'integrations',
|
|
43
|
+
'i18n',
|
|
44
|
+
'workflow',
|
|
45
|
+
'storage',
|
|
46
|
+
'ecommerce',
|
|
47
|
+
'projects',
|
|
48
|
+
'sms',
|
|
49
|
+
'forms',
|
|
50
|
+
'billing',
|
|
51
|
+
'intelligence',
|
|
52
|
+
'business',
|
|
53
|
+
'search',
|
|
54
|
+
'data',
|
|
55
|
+
'custom',
|
|
56
|
+
]);
|
|
57
|
+
// Sub-set of the engine's safety regexes — kept identical so the two paths
|
|
58
|
+
// agree. If we drift, an extension that validates clean would still be
|
|
59
|
+
// rejected at install time.
|
|
60
|
+
const SAFE_PACKAGE_NAME = /^(@[a-z0-9-_]+\/)?[a-z0-9-_.]+$/;
|
|
61
|
+
const SAFE_VERSION_RANGE = /^[\d.*^~>=<| -]+$/;
|
|
62
|
+
function err(code, message, file) {
|
|
63
|
+
return file ? { code, message, file } : { code, message };
|
|
64
|
+
}
|
|
65
|
+
export function validateManifest(input) {
|
|
66
|
+
const out = [];
|
|
67
|
+
const m = input.manifest;
|
|
68
|
+
if (m === null || typeof m !== 'object' || Array.isArray(m)) {
|
|
69
|
+
out.push(err('MANIFEST_NOT_OBJECT', 'manifest.json must be a JSON object'));
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
const obj = m;
|
|
73
|
+
// Required string fields
|
|
74
|
+
for (const k of ['name', 'displayName', 'category', 'description', 'version']) {
|
|
75
|
+
if (typeof obj[k] !== 'string' || obj[k].trim() === '') {
|
|
76
|
+
out.push(err('MANIFEST_MISSING_FIELD', `manifest.json missing required string field "${k}"`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const name = typeof obj.name === 'string' ? obj.name : '';
|
|
80
|
+
if (input.expectedName && name && name !== input.expectedName) {
|
|
81
|
+
out.push(err('MANIFEST_NAME_MISMATCH', `manifest "name": "${name}" does not match folder path "${input.expectedName}"`));
|
|
82
|
+
}
|
|
83
|
+
if (typeof obj.version === 'string' && !SEMVER_RE.test(obj.version)) {
|
|
84
|
+
out.push(err('MANIFEST_BAD_VERSION', `version "${obj.version}" is not valid semver (x.y.z)`));
|
|
85
|
+
}
|
|
86
|
+
if (typeof obj.zveltioMinVersion === 'string' && !SEMVER_RE.test(obj.zveltioMinVersion)) {
|
|
87
|
+
out.push(err('MANIFEST_BAD_MIN_VERSION', `zveltioMinVersion "${obj.zveltioMinVersion}" is not valid semver`));
|
|
88
|
+
}
|
|
89
|
+
if (typeof obj.zveltioMaxVersion === 'string' && !SEMVER_RE.test(obj.zveltioMaxVersion)) {
|
|
90
|
+
out.push(err('MANIFEST_BAD_MAX_VERSION', `zveltioMaxVersion "${obj.zveltioMaxVersion}" is not valid semver`));
|
|
91
|
+
}
|
|
92
|
+
if (typeof obj.category === 'string' && !KNOWN_CATEGORIES.has(obj.category)) {
|
|
93
|
+
// Warning rather than error — categories are open-ended in practice.
|
|
94
|
+
// Surface so authors notice typos like "finanace".
|
|
95
|
+
out.push(err('MANIFEST_UNKNOWN_CATEGORY', `category "${obj.category}" isn't in the known set — typo? Known: ${[...KNOWN_CATEGORIES].sort().join(', ')}`));
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
export function validatePeerDependencies(input) {
|
|
100
|
+
const out = [];
|
|
101
|
+
const deps = input.peerDependencies ?? {};
|
|
102
|
+
for (const [pkg, version] of Object.entries(deps)) {
|
|
103
|
+
if (typeof version !== 'string') {
|
|
104
|
+
out.push(err('PEERDEP_BAD_VERSION', `peerDependency "${pkg}" version must be a string, got ${typeof version}`));
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!SAFE_PACKAGE_NAME.test(pkg)) {
|
|
108
|
+
out.push(err('PEERDEP_UNSAFE_NAME', `peerDependency "${pkg}" uses an unsupported name format (file:, git:, etc. are forbidden)`));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (!SAFE_VERSION_RANGE.test(version)) {
|
|
112
|
+
out.push(err('PEERDEP_UNSAFE_VERSION', `peerDependency "${pkg}" version range "${version}" is not a plain semver range`));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!input.allowedPackages.has(pkg)) {
|
|
116
|
+
out.push(err('PEERDEP_NOT_ALLOWED', `peerDependency "${pkg}" is not on the platform allow-list — open a PR to peer-deps-allowlist.ts to request inclusion`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Detect "destructive" DDL statements in an UP section. Heuristic — we want
|
|
123
|
+
* to catch the easy cases (DROP TABLE, DROP COLUMN, ALTER COLUMN ... DROP)
|
|
124
|
+
* without false positives on benign DROP IF EXISTS in DOWN sections (which
|
|
125
|
+
* we don't scan).
|
|
126
|
+
*/
|
|
127
|
+
function hasDestructiveDdl(upSql) {
|
|
128
|
+
// Strip strings and comments-light: this is a heuristic; full SQL parsing
|
|
129
|
+
// is overkill for a pre-publish lint.
|
|
130
|
+
const s = upSql.replace(/--[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
131
|
+
if (/\bDROP\s+TABLE\b/i.test(s))
|
|
132
|
+
return true;
|
|
133
|
+
if (/\bDROP\s+COLUMN\b/i.test(s))
|
|
134
|
+
return true;
|
|
135
|
+
if (/\bALTER\s+COLUMN\b[\s\S]*?\bDROP\s+(NOT\s+NULL|DEFAULT)\b/i.test(s))
|
|
136
|
+
return true;
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
export function validateMigrations(input) {
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const file of input.files) {
|
|
142
|
+
if (file.sql.trim() === '') {
|
|
143
|
+
out.push(err('MIGRATION_EMPTY', 'migration file is empty', file.filename));
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = parseMigrationSql(file.sql);
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
out.push(err('MIGRATION_PARSE', `could not parse migration: ${e.message}`, file.filename));
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (parsed.up.trim() === '') {
|
|
155
|
+
out.push(err('MIGRATION_NO_UP', 'migration has no UP section (everything before -- DOWN)', file.filename));
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (input.requireDownForDestructive && hasDestructiveDdl(parsed.up) && !parsed.down) {
|
|
159
|
+
out.push(err('MIGRATION_DESTRUCTIVE_NO_DOWN', 'migration contains destructive DDL (DROP TABLE/COLUMN or ALTER ... DROP) but has no "-- DOWN" section. Add one so uninstall --purgeData can roll back.', file.filename));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
export function validateFilePresence(input) {
|
|
165
|
+
const out = [];
|
|
166
|
+
for (const p of input.required) {
|
|
167
|
+
if (!input.paths[p]) {
|
|
168
|
+
out.push(err('FILE_MISSING', `required file is missing`, p));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
const DEFAULT_BUNDLE_KB_MAX = 50_000;
|
|
174
|
+
export function validateBundleSize(input) {
|
|
175
|
+
const cap = input.bundleSizeKbMax ?? DEFAULT_BUNDLE_KB_MAX;
|
|
176
|
+
const observedKb = Math.ceil(input.bundleBytes / 1024);
|
|
177
|
+
if (observedKb > cap) {
|
|
178
|
+
return [
|
|
179
|
+
err('BUNDLE_TOO_LARGE', `extension bundle is ${observedKb} KB; quota is ${cap} KB. Reduce the bundle or raise the quota in manifest.quotas.bundleSizeKbMax.`),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
export function validateExtension(input) {
|
|
185
|
+
const errors = [
|
|
186
|
+
...validateManifest(input.manifest),
|
|
187
|
+
...validatePeerDependencies(input.peerDeps),
|
|
188
|
+
...validateMigrations(input.migrations),
|
|
189
|
+
...validateFilePresence(input.filePresence),
|
|
190
|
+
...(input.bundleSize ? validateBundleSize(input.bundleSize) : []),
|
|
191
|
+
];
|
|
192
|
+
return {
|
|
193
|
+
ok: errors.length === 0,
|
|
194
|
+
errors,
|
|
195
|
+
stats: {
|
|
196
|
+
tables: input.stats.tables,
|
|
197
|
+
migrations: input.stats.migrations,
|
|
198
|
+
peerDeps: Object.keys(input.peerDeps.peerDependencies ?? {}).length,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a single SQL migration file into its UP and DOWN sections.
|
|
3
|
+
*
|
|
4
|
+
* The DOWN section starts at the first line matching `-- DOWN` (case
|
|
5
|
+
* insensitive). Everything before that marker is UP; everything after the
|
|
6
|
+
* marker line is DOWN. If the marker is absent, the whole file is UP.
|
|
7
|
+
*
|
|
8
|
+
* Mirror of `packages/engine/src/lib/extension-loader.ts:parseMigrationSql`
|
|
9
|
+
* — kept in the SDK so validators and tooling can call it without depending
|
|
10
|
+
* on engine internals.
|
|
11
|
+
*/
|
|
12
|
+
export interface ParsedMigration {
|
|
13
|
+
up: string;
|
|
14
|
+
down: string | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function parseMigrationSql(raw: string): ParsedMigration;
|
|
17
|
+
//# sourceMappingURL=migration-parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-parse.d.ts","sourceRoot":"","sources":["../../src/validate/migration-parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAU9D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function parseMigrationSql(raw) {
|
|
2
|
+
const downIdx = raw.search(/^--\s*DOWN\b/im);
|
|
3
|
+
if (downIdx < 0) {
|
|
4
|
+
return { up: raw.trim(), down: null };
|
|
5
|
+
}
|
|
6
|
+
const up = raw.slice(0, downIdx).trim();
|
|
7
|
+
const downSection = raw.slice(downIdx);
|
|
8
|
+
const firstNewline = downSection.indexOf('\n');
|
|
9
|
+
const downBody = firstNewline >= 0 ? downSection.slice(firstNewline + 1).trim() : '';
|
|
10
|
+
return { up, down: downBody.length > 0 ? downBody : null };
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zveltio/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,6 +12,42 @@
|
|
|
12
12
|
"./extension": {
|
|
13
13
|
"types": "./dist/extension/index.d.ts",
|
|
14
14
|
"import": "./dist/extension/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./codegen": {
|
|
17
|
+
"types": "./dist/schema-codegen.d.ts",
|
|
18
|
+
"import": "./dist/schema-codegen.js"
|
|
19
|
+
},
|
|
20
|
+
"./validate": {
|
|
21
|
+
"types": "./dist/validate/index.d.ts",
|
|
22
|
+
"import": "./dist/validate/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./testing": {
|
|
25
|
+
"types": "./dist/testing/index.d.ts",
|
|
26
|
+
"import": "./dist/testing/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./publish": {
|
|
29
|
+
"types": "./dist/publish/index.d.ts",
|
|
30
|
+
"import": "./dist/publish/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./build": {
|
|
33
|
+
"types": "./dist/build/index.d.ts",
|
|
34
|
+
"import": "./dist/build/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./studio": {
|
|
37
|
+
"types": "./dist/studio/index.d.ts",
|
|
38
|
+
"import": "./dist/studio/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./ddl": {
|
|
41
|
+
"types": "./dist/ddl/index.d.ts",
|
|
42
|
+
"import": "./dist/ddl/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./rpc": {
|
|
45
|
+
"types": "./dist/rpc/index.d.ts",
|
|
46
|
+
"import": "./dist/rpc/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./offline": {
|
|
49
|
+
"types": "./dist/offline/index.d.ts",
|
|
50
|
+
"import": "./dist/offline/index.js"
|
|
15
51
|
}
|
|
16
52
|
},
|
|
17
53
|
"files": [
|
|
@@ -28,10 +64,26 @@
|
|
|
28
64
|
"idb": "^8.0.0"
|
|
29
65
|
},
|
|
30
66
|
"devDependencies": {
|
|
67
|
+
"@testcontainers/postgresql": "^11.0.0",
|
|
68
|
+
"@types/pg": "^8.11.0",
|
|
31
69
|
"fake-indexeddb": "^6.2.5",
|
|
32
70
|
"hono": "^4.12.5",
|
|
71
|
+
"kysely": "^0.27.0",
|
|
72
|
+
"pg": "^8.13.0",
|
|
33
73
|
"typescript": "^5.4.0"
|
|
34
74
|
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"@testcontainers/postgresql": "^11.0.0",
|
|
77
|
+
"pg": "^8.13.0"
|
|
78
|
+
},
|
|
79
|
+
"peerDependenciesMeta": {
|
|
80
|
+
"@testcontainers/postgresql": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"pg": {
|
|
84
|
+
"optional": true
|
|
85
|
+
}
|
|
86
|
+
},
|
|
35
87
|
"repository": {
|
|
36
88
|
"type": "git",
|
|
37
89
|
"url": "git+https://github.com/zveltio-devs/zveltio.git"
|