@void/deploy-core 0.21.9 → 0.23.0

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 CHANGED
@@ -9,6 +9,10 @@ manifest validation, and provider-neutral resource planning. It has no
9
9
  dependency on either deployment target and must remain usable from Node.js and
10
10
  Cloudflare Workers.
11
11
 
12
+ SQL migration tokenization, classification, and canonical hash inputs are shared
13
+ here. Each target supplies its hashing implementation and independently validates
14
+ submitted plans; CLI execution policy stays in the CLI.
15
+
12
16
  The package also defines the adapter boundary implemented by deployment
13
17
  targets. Target-specific orchestration belongs in `void` or `platform/`, not
14
18
  here.
package/dist/index.d.mts CHANGED
@@ -166,6 +166,88 @@ export declare function createDeployUploadPayload(artifact: DeployArtifact, meta
166
166
  preflight: "true" | null;
167
167
  }): DeployUploadPayload;
168
168
  //#endregion
169
+ //#region src/canonical-json.d.ts
170
+ export declare function canonicalJson(value: unknown): string;
171
+ //#endregion
172
+ //#region src/sql-tokenize.d.ts
173
+ /**
174
+ * Char-by-char SQL tokenizer shared by the destructive-SQL guard
175
+ * (projects.ts) and the single-statement classifier (remote-runner.ts).
176
+ *
177
+ * Walks the input and yields contiguous segments. `sql` runs are
178
+ * emitted as `{ kind: 'sql', start, end }` so callers can `sql.slice`
179
+ * without character-by-character allocations. Special regions are
180
+ * yielded whole with their delimiters included in `[start, end)`.
181
+ *
182
+ * Lexical forms handled (SQLite / drizzle-kit SQLite output):
183
+ * - `-- line` line comments (terminate at `\n` or EOF)
184
+ * - `/* block *\/` block comments (tolerate unterminated — span to EOF)
185
+ * - `'...'` string literals with `''` doubled-quote escape
186
+ * - `"..."` / `` `...` `` identifier quotes with `""` / `` `` `` escape
187
+ * - `[...]` bracket identifiers, NO `]]` escape — a single
188
+ * `]` closes the identifier (SQLite semantics;
189
+ * differs from MS SQL)
190
+ *
191
+ * Not handled — intentional, both current callers target SQLite only:
192
+ * - Postgres `$tag$ ... $tag$` dollar-quoting
193
+ * - Postgres `E'...'` extended string literals
194
+ *
195
+ * Adding a new quoting form anywhere should land here so both callers
196
+ * stay in lockstep.
197
+ */
198
+ type SqlSegment = {
199
+ kind: "sql";
200
+ start: number;
201
+ end: number;
202
+ } | {
203
+ kind: "line-comment";
204
+ start: number;
205
+ end: number;
206
+ } | {
207
+ kind: "block-comment";
208
+ start: number;
209
+ end: number;
210
+ } | {
211
+ kind: "string";
212
+ start: number;
213
+ end: number;
214
+ } | {
215
+ kind: "ident-quote";
216
+ open: "\"" | "`" | "[";
217
+ start: number;
218
+ end: number;
219
+ };
220
+ export declare function tokenizeSql(sql: string): Generator<SqlSegment>;
221
+ //#endregion
222
+ //#region src/migration-plan.d.ts
223
+ export declare const MIGRATION_CLASSIFIER_VERSION = 1;
224
+ export declare const UNSPLIT_MULTI_STATEMENT_PRAGMA = "-- void:allow-unsplit-multi-statement";
225
+ type SqlMigrationClassification = {
226
+ kind: "single-statement";
227
+ stability: "stable";
228
+ } | {
229
+ kind: "breakpoint-framed";
230
+ stability: "stable";
231
+ statements: Array<string>;
232
+ } | {
233
+ kind: "legacy-unsplit-multi-statement";
234
+ stability: "legacy";
235
+ allowLegacy: boolean;
236
+ };
237
+ type MigrationPlanHashInput = SqlMigrationClassification & {
238
+ name: string;
239
+ dialect: "sqlite";
240
+ classifierVersion: number;
241
+ sqlHash: string;
242
+ };
243
+ export declare function canonicalSql(sql: string): string;
244
+ export declare function hasStandalonePragma(sql: string, pragma: string): boolean;
245
+ export declare function isSingleStatement(sql: string): boolean;
246
+ /** Classify SQL without imposing the caller's execution policy or hashing implementation. */
247
+ export declare function classifySqlMigration(sql: string): SqlMigrationClassification;
248
+ /** Preserve the classifier-v1 hash preimage; policy markers and warnings are not hashed. */
249
+ export declare function getMigrationPlanHashInput(plan: MigrationPlanHashInput): unknown;
250
+ //#endregion
169
251
  //#region src/index.d.ts
170
252
  export declare const SUPPORTED_MANIFEST_VERSIONS: ReadonlyArray<number>;
171
253
  export type MigrationMeta = {
@@ -263,6 +345,8 @@ export type SandboxMigrationMeta = {
263
345
  newSqliteClasses?: ReadonlyArray<string>;
264
346
  deletedClasses?: ReadonlyArray<string>;
265
347
  };
348
+ /** Named images under the Durable Object scheduling policy must be digest-pinned. */
349
+ export declare function isSandboxRegistryImage(image: string): boolean;
266
350
  export type SandboxManifestMeta = {
267
351
  bindingName: string;
268
352
  className: string;
@@ -273,10 +357,10 @@ export type SandboxManifestMeta = {
273
357
  migrations?: ReadonlyArray<SandboxMigrationMeta>;
274
358
  /** Written by the managed platform, never accepted from deployment clients. */
275
359
  runtime?: {
276
- protocol: 1;
360
+ protocol: 1 | 2;
277
361
  };
278
362
  /** Managed transport implemented by the application's bundled Void runtime. */
279
- controllerProtocol?: 1;
363
+ controllerProtocol?: 1 | 2;
280
364
  };
281
365
  export type LiveMigrationMeta = {
282
366
  tag: string;
@@ -405,4 +489,4 @@ export type DeployEvent = {
405
489
  step: string;
406
490
  };
407
491
  //#endregion
408
- export type { DeployAppType, DeployArtifact, DeployAssetManifestEntry, DeployAssetUploadRequest, DeployCapabilitySupport, DeployIntent, DeployResource, DeployResourcePlan, DeployRuntimeFeatures, DeploySourceMetadata, DeployTarget, DeployTargetAdapter, DeployTargetCapabilities, DeployUploadPayload };
492
+ export type { DeployAppType, DeployArtifact, DeployAssetManifestEntry, DeployAssetUploadRequest, DeployCapabilitySupport, DeployIntent, DeployResource, DeployResourcePlan, DeployRuntimeFeatures, DeploySourceMetadata, DeployTarget, DeployTargetAdapter, DeployTargetCapabilities, DeployUploadPayload, MigrationPlanHashInput, SqlMigrationClassification, SqlSegment };
package/dist/index.mjs CHANGED
@@ -395,6 +395,213 @@ function createDeployUploadPayload(artifact, metadata) {
395
395
  };
396
396
  }
397
397
  //#endregion
398
+ //#region src/canonical-json.ts
399
+ function canonicalJson(value) {
400
+ if (value === null) return "null";
401
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return JSON.stringify(value);
402
+ if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
403
+ if (typeof value === "object") {
404
+ const object = value;
405
+ return `{${Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`).join(",")}}`;
406
+ }
407
+ throw new TypeError(`Unsupported canonical JSON value: ${typeof value}`);
408
+ }
409
+ //#endregion
410
+ //#region src/sql-tokenize.ts
411
+ function* tokenizeSql(sql) {
412
+ const sqlLength = sql.length;
413
+ let index = 0;
414
+ let runStart = 0;
415
+ const flushRun = (upTo) => {
416
+ if (upTo > runStart) return {
417
+ kind: "sql",
418
+ start: runStart,
419
+ end: upTo
420
+ };
421
+ };
422
+ while (index < sqlLength) {
423
+ const character = sql[index];
424
+ const next = index + 1 < sqlLength ? sql[index + 1] : "";
425
+ if (character === "-" && next === "-") {
426
+ const run = flushRun(index);
427
+ if (run) yield run;
428
+ let scanIndex = index + 2;
429
+ while (scanIndex < sqlLength && sql[scanIndex] !== "\n") scanIndex++;
430
+ yield {
431
+ kind: "line-comment",
432
+ start: index,
433
+ end: scanIndex
434
+ };
435
+ index = scanIndex;
436
+ runStart = index;
437
+ continue;
438
+ }
439
+ if (character === "/" && next === "*") {
440
+ const run = flushRun(index);
441
+ if (run) yield run;
442
+ let scanIndex = index + 2;
443
+ while (scanIndex < sqlLength - 1 && !(sql[scanIndex] === "*" && sql[scanIndex + 1] === "/")) scanIndex++;
444
+ const end = Math.min(scanIndex + 2, sqlLength);
445
+ yield {
446
+ kind: "block-comment",
447
+ start: index,
448
+ end
449
+ };
450
+ index = end;
451
+ runStart = index;
452
+ continue;
453
+ }
454
+ if (character === "'") {
455
+ const run = flushRun(index);
456
+ if (run) yield run;
457
+ let scanIndex = index + 1;
458
+ while (scanIndex < sqlLength) {
459
+ if (sql[scanIndex] === "'" && sql[scanIndex + 1] === "'") {
460
+ scanIndex += 2;
461
+ continue;
462
+ }
463
+ if (sql[scanIndex] === "'") {
464
+ scanIndex++;
465
+ break;
466
+ }
467
+ scanIndex++;
468
+ }
469
+ yield {
470
+ kind: "string",
471
+ start: index,
472
+ end: scanIndex
473
+ };
474
+ index = scanIndex;
475
+ runStart = index;
476
+ continue;
477
+ }
478
+ if (character === "\"" || character === "`" || character === "[") {
479
+ const run = flushRun(index);
480
+ if (run) yield run;
481
+ const close = character === "[" ? "]" : character;
482
+ let scanIndex = index + 1;
483
+ while (scanIndex < sqlLength) {
484
+ if (close !== "]" && sql[scanIndex] === close && sql[scanIndex + 1] === close) {
485
+ scanIndex += 2;
486
+ continue;
487
+ }
488
+ if (sql[scanIndex] === close) {
489
+ scanIndex++;
490
+ break;
491
+ }
492
+ scanIndex++;
493
+ }
494
+ yield {
495
+ kind: "ident-quote",
496
+ open: character,
497
+ start: index,
498
+ end: scanIndex
499
+ };
500
+ index = scanIndex;
501
+ runStart = index;
502
+ continue;
503
+ }
504
+ index++;
505
+ }
506
+ const tail = flushRun(sqlLength);
507
+ if (tail) yield tail;
508
+ }
509
+ //#endregion
510
+ //#region src/migration-plan.ts
511
+ const MIGRATION_CLASSIFIER_VERSION = 1;
512
+ const UNSPLIT_MULTI_STATEMENT_PRAGMA = "-- void:allow-unsplit-multi-statement";
513
+ function canonicalSql(sql) {
514
+ return (sql.charCodeAt(0) === 65279 ? sql.slice(1) : sql).replace(/\r\n?/g, "\n");
515
+ }
516
+ function isStandalonePragmaLine(line, pragma) {
517
+ return line.trim() === pragma;
518
+ }
519
+ function hasStandalonePragma(sql, pragma) {
520
+ return canonicalSql(sql).split("\n").some((line) => isStandalonePragmaLine(line, pragma));
521
+ }
522
+ function isBreakpointComment(text) {
523
+ return text.trim() === "--> statement-breakpoint";
524
+ }
525
+ function splitBreakpointFramedSql(sql) {
526
+ const normalized = canonicalSql(sql);
527
+ const statements = [];
528
+ let cursor = 0;
529
+ for (const segment of tokenizeSql(normalized)) {
530
+ if (segment.kind !== "line-comment") continue;
531
+ if (isBreakpointComment(normalized.slice(segment.start, segment.end))) {
532
+ const statement = normalized.slice(cursor, segment.start).trim();
533
+ if (statement) statements.push(statement);
534
+ cursor = segment.end;
535
+ }
536
+ }
537
+ const statement = normalized.slice(cursor).trim();
538
+ if (statement) statements.push(statement);
539
+ return statements;
540
+ }
541
+ function hasStatementBreakpointOutsideLiterals(sql) {
542
+ const normalized = canonicalSql(sql);
543
+ for (const segment of tokenizeSql(normalized)) {
544
+ if (segment.kind !== "line-comment") continue;
545
+ if (isBreakpointComment(normalized.slice(segment.start, segment.end))) return true;
546
+ }
547
+ return false;
548
+ }
549
+ function isSingleStatement(sql) {
550
+ const normalized = canonicalSql(sql);
551
+ let statements = 0;
552
+ let inStatement = false;
553
+ for (const segment of tokenizeSql(normalized)) {
554
+ if (segment.kind === "line-comment" || segment.kind === "block-comment") continue;
555
+ if (segment.kind === "string" || segment.kind === "ident-quote") {
556
+ inStatement = true;
557
+ continue;
558
+ }
559
+ for (let index = segment.start; index < segment.end; index++) {
560
+ const character = normalized[index];
561
+ if (character === ";") {
562
+ if (inStatement) {
563
+ statements++;
564
+ inStatement = false;
565
+ }
566
+ continue;
567
+ }
568
+ if (character !== " " && character !== " " && character !== "\n" && character !== "\r") inStatement = true;
569
+ }
570
+ }
571
+ if (inStatement) statements++;
572
+ return statements === 1;
573
+ }
574
+ /** Classify SQL without imposing the caller's execution policy or hashing implementation. */
575
+ function classifySqlMigration(sql) {
576
+ if (hasStatementBreakpointOutsideLiterals(sql)) return {
577
+ kind: "breakpoint-framed",
578
+ stability: "stable",
579
+ statements: splitBreakpointFramedSql(sql)
580
+ };
581
+ if (isSingleStatement(sql)) return {
582
+ kind: "single-statement",
583
+ stability: "stable"
584
+ };
585
+ return {
586
+ kind: "legacy-unsplit-multi-statement",
587
+ stability: "legacy",
588
+ allowLegacy: hasStandalonePragma(sql, UNSPLIT_MULTI_STATEMENT_PRAGMA)
589
+ };
590
+ }
591
+ /** Preserve the classifier-v1 hash preimage; policy markers and warnings are not hashed. */
592
+ function getMigrationPlanHashInput(plan) {
593
+ const base = {
594
+ classifierVersion: plan.classifierVersion,
595
+ dialect: plan.dialect,
596
+ kind: plan.kind,
597
+ name: plan.name,
598
+ sqlHash: plan.sqlHash,
599
+ stability: plan.stability
600
+ };
601
+ if (plan.kind === "breakpoint-framed") base.statements = plan.statements;
602
+ return base;
603
+ }
604
+ //#endregion
398
605
  //#region src/index.ts
399
606
  const SUPPORTED_MANIFEST_VERSIONS = [3];
400
607
  /**
@@ -470,5 +677,9 @@ function sortUniqueStrings(values) {
470
677
  function sameStringSet(a, b) {
471
678
  return a.length === b.length && a.every((value, index) => value === b[index]);
472
679
  }
680
+ /** Named images under the Durable Object scheduling policy must be digest-pinned. */
681
+ function isSandboxRegistryImage(image) {
682
+ return /^registry\.cloudflare\.com\/[^/@\s]+\/[^@\s]+@sha256:[a-f\d]{64}$/.test(image);
683
+ }
473
684
  //#endregion
474
- export { DEPLOY_ARTIFACT_VERSION, MANIFEST_REDIRECT_RULE_STATUSES, MAX_REVALIDATE_QUERY_ALLOWLIST_ENTRIES, MAX_ROUTING_RULES, SUPPORTED_MANIFEST_VERSIONS, appendSqliteClassMigration, createDeployArtifact, createDeployIntent, createDeployResourcePlan, createDeployUploadPayload, planWebSocketMigrations, validateManifestShape };
685
+ export { DEPLOY_ARTIFACT_VERSION, MANIFEST_REDIRECT_RULE_STATUSES, MAX_REVALIDATE_QUERY_ALLOWLIST_ENTRIES, MAX_ROUTING_RULES, MIGRATION_CLASSIFIER_VERSION, SUPPORTED_MANIFEST_VERSIONS, UNSPLIT_MULTI_STATEMENT_PRAGMA, appendSqliteClassMigration, canonicalJson, canonicalSql, classifySqlMigration, createDeployArtifact, createDeployIntent, createDeployResourcePlan, createDeployUploadPayload, getMigrationPlanHashInput, hasStandalonePragma, isSandboxRegistryImage, isSingleStatement, planWebSocketMigrations, tokenizeSql, validateManifestShape };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/deploy-core",
3
- "version": "0.21.9",
3
+ "version": "0.23.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "typescript": "7.0.2",
24
- "vite-plus": "0.3.1"
24
+ "vite-plus": "1.0.0"
25
25
  },
26
26
  "scripts": {
27
27
  "build": "vp pack",