@happyvertical/smrt-core 0.55.6 → 0.55.8
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/AGENTS.md +2 -1
- package/agents/browser-entry.md +44 -0
- package/agents/build-knowledge.md +20 -0
- package/agents/recipes.md +217 -0
- package/agents/revision-guard.md +72 -1
- package/dist/audit-context.d.ts.map +1 -1
- package/dist/audit-context.js +2 -2
- package/dist/audit-context.js.map +1 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +7 -7
- package/dist/cascade.d.ts +1 -1
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +1 -1
- package/dist/cascade.js.map +1 -1
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +1 -2
- package/dist/change-feed.js.map +1 -1
- package/dist/class.d.ts +1 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +7 -3
- package/dist/class.js.map +1 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +1 -1
- package/dist/collection.js.map +1 -1
- package/dist/consumer-plugin/index.js +2 -2
- package/dist/data-query.d.ts.map +1 -1
- package/dist/data-query.js +2 -13
- package/dist/data-query.js.map +1 -1
- package/dist/database.js +1 -1
- package/dist/database.js.map +1 -1
- package/dist/dispatch/bus.js +1 -1
- package/dist/dispatch/bus.js.map +1 -1
- package/dist/embedded-write-queue.d.ts +1 -1
- package/dist/embedded-write-queue.d.ts.map +1 -1
- package/dist/embedded-write-queue.js +7 -5
- package/dist/embedded-write-queue.js.map +1 -1
- package/dist/embeddings/hash.js +2 -2
- package/dist/embeddings/hash.js.map +1 -1
- package/dist/embeddings/storage.d.ts.map +1 -1
- package/dist/embeddings/storage.js +1 -2
- package/dist/embeddings/storage.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/generation-snapshot.js +1 -1
- package/dist/generators/index.js +1 -1
- package/dist/generators/mcp.js +3 -3
- package/dist/generators/preflight-route.js +1 -1
- package/dist/generators/rest.js +1 -1
- package/dist/generators.js +1 -1
- package/dist/host.browser.d.ts +12 -0
- package/dist/host.browser.d.ts.map +1 -0
- package/dist/host.browser.js +30 -0
- package/dist/host.browser.js.map +1 -0
- package/dist/host.d.ts +18 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +15 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -9
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +6 -4
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/browser-capability.generated.d.ts +14 -0
- package/dist/manifest/browser-capability.generated.d.ts.map +1 -0
- package/dist/manifest/browser-capability.generated.js +499 -0
- package/dist/manifest/browser-capability.generated.js.map +1 -0
- package/dist/manifest/discover-base-classes.js +1 -1
- package/dist/manifest/discover-smrt-packages.js +1 -1
- package/dist/manifest/generator.js +3 -3
- package/dist/manifest/index.js +2 -2
- package/dist/manifest/local-manifest-reader.d.ts +48 -0
- package/dist/manifest/local-manifest-reader.d.ts.map +1 -0
- package/dist/manifest/local-manifest-reader.js +81 -0
- package/dist/manifest/local-manifest-reader.js.map +1 -0
- package/dist/manifest/manager.d.ts +5 -28
- package/dist/manifest/manager.d.ts.map +1 -1
- package/dist/manifest/manager.js +10 -56
- package/dist/manifest/manager.js.map +1 -1
- package/dist/manifest/manifest-loader.d.ts +1 -1
- package/dist/manifest/manifest-loader.d.ts.map +1 -1
- package/dist/manifest/manifest-loader.js +43 -12
- package/dist/manifest/manifest-loader.js.map +1 -1
- package/dist/manifest/node-host.d.ts +38 -0
- package/dist/manifest/node-host.d.ts.map +1 -0
- package/dist/manifest/node-host.js +52 -0
- package/dist/manifest/node-host.js.map +1 -0
- package/dist/manifest/package-manifest-exports.js +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +12 -2
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.d.ts +2 -1
- package/dist/manifest/store.d.ts.map +1 -1
- package/dist/manifest/store.js +7 -4
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.js +1 -1
- package/dist/manifest.json +14 -2
- package/dist/native-duckdb.d.ts +28 -0
- package/dist/native-duckdb.d.ts.map +1 -0
- package/dist/native-duckdb.js +51 -0
- package/dist/native-duckdb.js.map +1 -0
- package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/_md.js +169 -0
- package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/_md.js.map +1 -0
- package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/_u64.js +13 -0
- package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/_u64.js.map +1 -0
- package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/sha2.js +198 -0
- package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/sha2.js.map +1 -0
- package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/utils.js +243 -0
- package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/utils.js.map +1 -0
- package/dist/object.d.ts +56 -0
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +139 -16
- package/dist/object.js.map +1 -1
- package/dist/postgres-permissions.d.ts.map +1 -1
- package/dist/postgres-permissions.js +3 -3
- package/dist/postgres-permissions.js.map +1 -1
- package/dist/prebuild/index.js +1 -1
- package/dist/recipe-demo.d.ts +70 -0
- package/dist/recipe-demo.d.ts.map +1 -0
- package/dist/recipe-demo.js +130 -0
- package/dist/recipe-demo.js.map +1 -0
- package/dist/recipe.d.ts +64 -1
- package/dist/recipe.d.ts.map +1 -1
- package/dist/recipe.js +30 -0
- package/dist/recipe.js.map +1 -1
- package/dist/registry/class-registration.js +2 -2
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/db-cache-key.js +4 -5
- package/dist/registry/db-cache-key.js.map +1 -1
- package/dist/registry/inheritance-resolver.js +1 -1
- package/dist/registry/name-resolver.js +1 -1
- package/dist/registry/qualified-name-aliases.js +1 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +3 -3
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/types.d.ts +5 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +5 -8
- package/dist/registry.js.map +1 -1
- package/dist/revision-guard.js +1 -1
- package/dist/revision-guard.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +19 -10
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/recipe-demo-pass.d.ts +14 -0
- package/dist/scanner/recipe-demo-pass.d.ts.map +1 -0
- package/dist/scanner/recipe-demo-pass.js +33 -0
- package/dist/scanner/recipe-demo-pass.js.map +1 -0
- package/dist/scanner/types.d.ts +9 -1
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +1 -1
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/foreign-key-planner.d.ts.map +1 -1
- package/dist/schema/foreign-key-planner.js +1 -1
- package/dist/schema/foreign-key-planner.js.map +1 -1
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +10 -14
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/schema-aggregator.js +1 -1
- package/dist/scripts/browser-entry-evaluation.mjs +157 -0
- package/dist/smrt-knowledge.json +32 -12
- package/dist/system/browser.d.ts +16 -0
- package/dist/system/browser.d.ts.map +1 -0
- package/dist/system/browser.js +6 -0
- package/dist/system/index.d.ts +1 -6
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +2 -1
- package/dist/testing/database.js +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/unchanged-indexed.d.ts +85 -0
- package/dist/unchanged-indexed.d.ts.map +1 -0
- package/dist/unchanged-indexed.js +219 -0
- package/dist/unchanged-indexed.js.map +1 -0
- package/dist/utils/async-context.d.ts +22 -0
- package/dist/utils/async-context.d.ts.map +1 -0
- package/dist/utils/async-context.js +27 -0
- package/dist/utils/async-context.js.map +1 -0
- package/dist/utils/import-project-vite.js +1 -1
- package/dist/utils/import-workspace-module.js +1 -1
- package/dist/utils/json.browser.d.ts +18 -0
- package/dist/utils/json.browser.d.ts.map +1 -0
- package/dist/utils/json.browser.js +47 -0
- package/dist/utils/json.browser.js.map +1 -0
- package/dist/utils/sha256.d.ts +15 -0
- package/dist/utils/sha256.d.ts.map +1 -0
- package/dist/utils/sha256.js +29 -0
- package/dist/utils/sha256.js.map +1 -0
- package/dist/vite-plugin/changes-route.js +1 -1
- package/dist/vite-plugin/dev-plane-route.js +1 -1
- package/dist/vite-plugin/events-route.js +1 -1
- package/dist/vite-plugin/import-build-aware.js +1 -1
- package/dist/vite-plugin/index.js +2 -2
- package/dist/vite-plugin/resources-route.js +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +2 -2
- package/dist/vite-plugin/sveltekit-path.js +1 -1
- package/dist/vite-plugin/sveltekit-register-injection.js +1 -1
- package/dist/vite-plugin/sveltekit-route-coordinator.js +1 -1
- package/dist/vite-plugin/sync-apply-route.js +1 -1
- package/dist/vite-plugin/web-collections.js +2 -2
- package/package.json +21 -11
package/dist/testing/database.js
CHANGED
|
@@ -3,11 +3,11 @@ import { detectEngine } from "../schema/ddl/index.js";
|
|
|
3
3
|
import { getSystemTableDDL } from "../system/schema.js";
|
|
4
4
|
import { ensurePostgresChangeFeedAppendFunction } from "../change-feed.js";
|
|
5
5
|
import { ensureLegacySystemTableCompatibility } from "../system/compatibility.js";
|
|
6
|
-
import { SchemaGenerator } from "../schema/generator.js";
|
|
7
6
|
import { recordRegistryDiagnostic } from "../registry/diagnostics.js";
|
|
8
7
|
import { isFrameworkBaseClass } from "../registry/framework-base-classes.js";
|
|
9
8
|
import { isCollectionRegistration, resolveCollectionItemClassName, resolveRelatedRegistration } from "../registry/collection-resolution.js";
|
|
10
9
|
import { ObjectRegistry } from "../registry.js";
|
|
10
|
+
import { SchemaGenerator } from "../schema/generator.js";
|
|
11
11
|
import { planForeignKeyCreation } from "../schema/foreign-key-planner.js";
|
|
12
12
|
import { getDatabase } from "@happyvertical/sql";
|
|
13
13
|
//#region src/testing/database.ts
|
package/dist/tools/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { convertTypeToJsonSchema, generateToolFromMethod, generateToolManifest, shouldIncludeMethod } from "./tool-generator.js";
|
|
2
1
|
import { executeToolCall, executeToolCalls, formatToolResults, validateToolCall } from "./tool-executor.js";
|
|
2
|
+
import { convertTypeToJsonSchema, generateToolFromMethod, generateToolManifest, shouldIncludeMethod } from "./tool-generator.js";
|
|
3
3
|
export { convertTypeToJsonSchema, executeToolCall, executeToolCalls, formatToolResults, generateToolFromMethod, generateToolManifest, shouldIncludeMethod, validateToolCall };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unchanged indexed assignments on persisted rows (#3737).
|
|
3
|
+
*
|
|
4
|
+
* DuckDB rewrites a row (delete + insert) when an INDEXED column is assigned,
|
|
5
|
+
* even with its existing value. A parent that a foreign key still references
|
|
6
|
+
* therefore raises a referenced-parent violation for `SET slug = <same slug>`.
|
|
7
|
+
* `SmrtObject.save()` serializes the whole model, so every existing-row write
|
|
8
|
+
* used to assign `slug`, `context`, `tenant_id`, `created_at`, ... unchanged.
|
|
9
|
+
*
|
|
10
|
+
* This module holds the pure decisions: which columns are indexed, whether a
|
|
11
|
+
* pending value provably equals the stored one, and how an assignment value
|
|
12
|
+
* is bound by the generic UPDATE API. Nothing here touches a database, so the
|
|
13
|
+
* save path keeps owning revision checks, tenant isolation and error typing.
|
|
14
|
+
*
|
|
15
|
+
* Everything errs toward "assign as before": an unknown column, a missing
|
|
16
|
+
* stored value or a value that cannot be compared is NOT reported unchanged.
|
|
17
|
+
*/
|
|
18
|
+
import type { SchemaDefinition } from './schema/types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Columns whose assignment DuckDB may treat as an index rewrite.
|
|
21
|
+
*
|
|
22
|
+
* The union of what the registry schema declares (primary key, UNIQUE, every
|
|
23
|
+
* declared index including partial and JSON-path ones, foreign keys) and the
|
|
24
|
+
* natural-key / ownership columns the runtime itself keys writes by. Being
|
|
25
|
+
* over-inclusive is harmless — a column is only skipped when it is also proven
|
|
26
|
+
* unchanged — so this never has to be an exact model of the physical DDL.
|
|
27
|
+
*/
|
|
28
|
+
export declare function collectIndexedColumns(schemas: ReadonlyArray<SchemaDefinition | undefined>, runtimeKeyColumns: Iterable<string>): Set<string>;
|
|
29
|
+
/**
|
|
30
|
+
* An instant as whole microseconds since the epoch, or `undefined` when it
|
|
31
|
+
* cannot be established exactly.
|
|
32
|
+
*
|
|
33
|
+
* A `Date` holds milliseconds. Text is read at its full written precision;
|
|
34
|
+
* digits beyond microseconds must be zero. Text without a zone is a wall
|
|
35
|
+
* clock in the process zone unless `naiveIsUtc` (DuckDB's `TIMESTAMP` text,
|
|
36
|
+
* which this runtime always writes as UTC).
|
|
37
|
+
*/
|
|
38
|
+
export declare function timestampMicros(value: unknown, naiveIsUtc?: boolean): bigint | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Whether every number literal in JSON text survives `JSON.parse` exactly.
|
|
41
|
+
*
|
|
42
|
+
* `JSON.parse` reads numbers as doubles, so `9007199254740993` and
|
|
43
|
+
* `9007199254740992` parse to the same value. A literal is exact when the
|
|
44
|
+
* double it parses to prints back as the same text; anything else (unsafe
|
|
45
|
+
* integers, more digits than a double holds, `1.0`, `1e2`) makes the text
|
|
46
|
+
* unprovable. Digits inside JSON strings are not numbers and are skipped.
|
|
47
|
+
*/
|
|
48
|
+
export declare function jsonNumbersRoundTrip(text: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Whether `next` (the value about to be written) provably equals `stored`
|
|
51
|
+
* (the canonical value read back from the row). `false` means "assign it":
|
|
52
|
+
* either it differs or equality cannot be established.
|
|
53
|
+
*/
|
|
54
|
+
export declare function storedValueEquals(columnType: string | undefined, next: unknown, stored: unknown, exactStoredTimestamp?: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* `TIMESTAMP` columns of `data` whose equality needs the stored value at full
|
|
57
|
+
* precision (see {@link storedValueEquals}); the caller reads them as text.
|
|
58
|
+
*/
|
|
59
|
+
export declare function timestampColumnsToProve(data: Readonly<Record<string, unknown>>, stored: Readonly<Record<string, unknown>>, indexedColumns: ReadonlySet<string>, columnTypes: Readonly<Record<string, {
|
|
60
|
+
type?: string;
|
|
61
|
+
} | undefined>>): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Indexed columns of `data` whose value provably equals the stored row's, in
|
|
64
|
+
* snake_case column names. `id` and `updated_at` are never reported: the
|
|
65
|
+
* revision token always advances, and `id` is the write's key.
|
|
66
|
+
*/
|
|
67
|
+
export declare function unchangedIndexedColumns(data: Readonly<Record<string, unknown>>, stored: Readonly<Record<string, unknown>>, indexedColumns: ReadonlySet<string>, columnTypes: Readonly<Record<string, {
|
|
68
|
+
type?: string;
|
|
69
|
+
} | undefined>>, exactTimestamps?: Readonly<Record<string, string>>): Set<string>;
|
|
70
|
+
/**
|
|
71
|
+
* An id-targeted `UPDATE` for native DuckDB, with every identifier quoted and
|
|
72
|
+
* every value bound the way the DuckDB adapter's `upsert()` binds its
|
|
73
|
+
* `DO UPDATE SET` values: `NULL` literals, `''` cast to TEXT, dates as ISO
|
|
74
|
+
* strings, arrays and plain objects as JSON text.
|
|
75
|
+
*
|
|
76
|
+
* The adapter's generic `update()` is not used because it renders column names
|
|
77
|
+
* unquoted (a column named `order` or `group` is a syntax error) and hands
|
|
78
|
+
* dates and structures to the driver untouched; the `upsert()` it replaces
|
|
79
|
+
* handled both.
|
|
80
|
+
*/
|
|
81
|
+
export declare function buildDuckDbIdUpdate(table: string, id: unknown, assignments: Readonly<Record<string, unknown>>): {
|
|
82
|
+
sql: string;
|
|
83
|
+
values: unknown[];
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=unchanged-indexed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unchanged-indexed.d.ts","sourceRoot":"","sources":["../src/unchanged-indexed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAM1D;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,CAAC,gBAAgB,GAAG,SAAS,CAAC,EACpD,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,GAClC,GAAG,CAAC,MAAM,CAAC,CAuBb;AAWD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,UAAU,UAAQ,GACjB,MAAM,GAAG,SAAS,CAwCpB;AAID;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAuB1D;AA8BD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,OAAO,EACf,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAwCT;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACvC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAAC,GACnE,MAAM,EAAE,CAUV;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACvC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAAC,EACpE,eAAe,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GACrD,GAAG,CAAC,MAAM,CAAC,CAkBb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,OAAO,EACX,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC7C;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAgCpC"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { toSnakeCase } from "./utils/naming.js";
|
|
2
|
+
import "./utils.js";
|
|
3
|
+
//#region src/unchanged-indexed.ts
|
|
4
|
+
/** Columns the save path always writes, whatever their stored value. */
|
|
5
|
+
var ALWAYS_ASSIGNED = /* @__PURE__ */ new Set(["id", "updated_at"]);
|
|
6
|
+
/**
|
|
7
|
+
* Columns whose assignment DuckDB may treat as an index rewrite.
|
|
8
|
+
*
|
|
9
|
+
* The union of what the registry schema declares (primary key, UNIQUE, every
|
|
10
|
+
* declared index including partial and JSON-path ones, foreign keys) and the
|
|
11
|
+
* natural-key / ownership columns the runtime itself keys writes by. Being
|
|
12
|
+
* over-inclusive is harmless — a column is only skipped when it is also proven
|
|
13
|
+
* unchanged — so this never has to be an exact model of the physical DDL.
|
|
14
|
+
*/
|
|
15
|
+
function collectIndexedColumns(schemas, runtimeKeyColumns) {
|
|
16
|
+
const indexed = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const column of runtimeKeyColumns) indexed.add(/[A-Z]/u.test(column) ? toSnakeCase(column) : column);
|
|
18
|
+
for (const schema of schemas) {
|
|
19
|
+
if (!schema) continue;
|
|
20
|
+
for (const [name, column] of Object.entries(schema.columns ?? {})) if (column.primaryKey || column.unique || column.foreignKey) indexed.add(name);
|
|
21
|
+
for (const index of schema.indexes ?? []) {
|
|
22
|
+
for (const name of index.columns ?? []) indexed.add(name);
|
|
23
|
+
if (index.jsonPath?.column) indexed.add(index.jsonPath.column);
|
|
24
|
+
}
|
|
25
|
+
for (const foreignKey of schema.foreignKeys ?? []) indexed.add(foreignKey.column);
|
|
26
|
+
}
|
|
27
|
+
return indexed;
|
|
28
|
+
}
|
|
29
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
30
|
+
var TIMESTAMP_TEXT = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?\s*(Z|[+-]\d{2}(?::?\d{2})?)?$/iu;
|
|
31
|
+
function isNullish(value) {
|
|
32
|
+
return value === null || value === void 0;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* An instant as whole microseconds since the epoch, or `undefined` when it
|
|
36
|
+
* cannot be established exactly.
|
|
37
|
+
*
|
|
38
|
+
* A `Date` holds milliseconds. Text is read at its full written precision;
|
|
39
|
+
* digits beyond microseconds must be zero. Text without a zone is a wall
|
|
40
|
+
* clock in the process zone unless `naiveIsUtc` (DuckDB's `TIMESTAMP` text,
|
|
41
|
+
* which this runtime always writes as UTC).
|
|
42
|
+
*/
|
|
43
|
+
function timestampMicros(value, naiveIsUtc = false) {
|
|
44
|
+
if (value instanceof Date) {
|
|
45
|
+
const time = value.getTime();
|
|
46
|
+
return Number.isNaN(time) ? void 0 : BigInt(time) * 1000n;
|
|
47
|
+
}
|
|
48
|
+
if (typeof value !== "string") return void 0;
|
|
49
|
+
const match = TIMESTAMP_TEXT.exec(value.trim());
|
|
50
|
+
if (!match) return void 0;
|
|
51
|
+
const [, year, month, day, hour, minute, second, fraction = "", zone] = match;
|
|
52
|
+
if (fraction.length > 6 && /[1-9]/u.test(fraction.slice(6))) return void 0;
|
|
53
|
+
if (zone === void 0 && !naiveIsUtc) return void 0;
|
|
54
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
55
|
+
date.setUTCFullYear(Number(year), Number(month) - 1, Number(day));
|
|
56
|
+
date.setUTCHours(Number(hour), Number(minute), Number(second), 0);
|
|
57
|
+
const wall = date.getTime();
|
|
58
|
+
if (Number.isNaN(wall)) return void 0;
|
|
59
|
+
if (date.getUTCFullYear() !== Number(year) || date.getUTCMonth() !== Number(month) - 1 || date.getUTCDate() !== Number(day) || date.getUTCHours() !== Number(hour) || date.getUTCMinutes() !== Number(minute) || date.getUTCSeconds() !== Number(second)) return;
|
|
60
|
+
let offsetMinutes = 0;
|
|
61
|
+
if (zone && zone.toUpperCase() !== "Z") {
|
|
62
|
+
const digits = zone.slice(1).replace(":", "");
|
|
63
|
+
const offsetHours = Number(digits.slice(0, 2));
|
|
64
|
+
const offsetRest = Number(digits.slice(2) || "0");
|
|
65
|
+
if (offsetHours > 23 || offsetRest > 59) return void 0;
|
|
66
|
+
offsetMinutes = (zone.startsWith("-") ? -1 : 1) * (offsetHours * 60 + offsetRest);
|
|
67
|
+
}
|
|
68
|
+
const micros = BigInt(fraction.slice(0, 6).padEnd(6, "0"));
|
|
69
|
+
return (BigInt(wall) - BigInt(offsetMinutes) * 60000n) * 1000n + micros;
|
|
70
|
+
}
|
|
71
|
+
var JSON_NUMBER = /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
|
|
72
|
+
/**
|
|
73
|
+
* Whether every number literal in JSON text survives `JSON.parse` exactly.
|
|
74
|
+
*
|
|
75
|
+
* `JSON.parse` reads numbers as doubles, so `9007199254740993` and
|
|
76
|
+
* `9007199254740992` parse to the same value. A literal is exact when the
|
|
77
|
+
* double it parses to prints back as the same text; anything else (unsafe
|
|
78
|
+
* integers, more digits than a double holds, `1.0`, `1e2`) makes the text
|
|
79
|
+
* unprovable. Digits inside JSON strings are not numbers and are skipped.
|
|
80
|
+
*/
|
|
81
|
+
function jsonNumbersRoundTrip(text) {
|
|
82
|
+
let inString = false;
|
|
83
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
84
|
+
const char = text[index];
|
|
85
|
+
if (inString) {
|
|
86
|
+
if (char === "\\") index += 1;
|
|
87
|
+
else if (char === "\"") inString = false;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (char === "\"") {
|
|
91
|
+
inString = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (char === "-" || char >= "0" && char <= "9") {
|
|
95
|
+
JSON_NUMBER.lastIndex = index;
|
|
96
|
+
const match = JSON_NUMBER.exec(text);
|
|
97
|
+
if (!match) return false;
|
|
98
|
+
const literal = match[0];
|
|
99
|
+
if (JSON.stringify(Number(literal)) !== literal) return false;
|
|
100
|
+
index += literal.length - 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
function canonicalJson(value) {
|
|
106
|
+
let parsed = value;
|
|
107
|
+
if (typeof value === "string") {
|
|
108
|
+
if (!jsonNumbersRoundTrip(value)) return void 0;
|
|
109
|
+
try {
|
|
110
|
+
parsed = JSON.parse(value);
|
|
111
|
+
} catch {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const normalize = (node) => {
|
|
116
|
+
if (Array.isArray(node)) return node.map(normalize);
|
|
117
|
+
if (node !== null && typeof node === "object") return Object.fromEntries(Object.entries(node).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, child]) => [key, normalize(child)]));
|
|
118
|
+
return node;
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
return JSON.stringify(normalize(parsed));
|
|
122
|
+
} catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Whether `next` (the value about to be written) provably equals `stored`
|
|
128
|
+
* (the canonical value read back from the row). `false` means "assign it":
|
|
129
|
+
* either it differs or equality cannot be established.
|
|
130
|
+
*/
|
|
131
|
+
function storedValueEquals(columnType, next, stored, exactStoredTimestamp) {
|
|
132
|
+
if (isNullish(next) || isNullish(stored)) return isNullish(next) && isNullish(stored);
|
|
133
|
+
const type = (columnType ?? "").toUpperCase();
|
|
134
|
+
if (type === "TIMESTAMP") {
|
|
135
|
+
if (exactStoredTimestamp === void 0) return false;
|
|
136
|
+
const a = timestampMicros(next);
|
|
137
|
+
const b = timestampMicros(exactStoredTimestamp, true);
|
|
138
|
+
return a !== void 0 && b !== void 0 && a === b;
|
|
139
|
+
}
|
|
140
|
+
if (type === "JSON") {
|
|
141
|
+
const a = canonicalJson(next);
|
|
142
|
+
const b = canonicalJson(stored);
|
|
143
|
+
return a !== void 0 && b !== void 0 && a === b;
|
|
144
|
+
}
|
|
145
|
+
if (typeof next === "string" && typeof stored === "string") {
|
|
146
|
+
if (type === "UUID" && UUID_PATTERN.test(next) && UUID_PATTERN.test(stored)) return next.toLowerCase() === stored.toLowerCase();
|
|
147
|
+
return next === stored;
|
|
148
|
+
}
|
|
149
|
+
if (typeof next === "boolean" && typeof stored === "boolean") return next === stored;
|
|
150
|
+
const numeric = (value) => typeof value === "bigint" || typeof value === "number" ? value : void 0;
|
|
151
|
+
const a = numeric(next);
|
|
152
|
+
const b = numeric(stored);
|
|
153
|
+
if (a !== void 0 && b !== void 0) return a == b;
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* `TIMESTAMP` columns of `data` whose equality needs the stored value at full
|
|
158
|
+
* precision (see {@link storedValueEquals}); the caller reads them as text.
|
|
159
|
+
*/
|
|
160
|
+
function timestampColumnsToProve(data, stored, indexedColumns, columnTypes) {
|
|
161
|
+
return Object.keys(data).filter((column) => !ALWAYS_ASSIGNED.has(column) && indexedColumns.has(column) && Object.hasOwn(stored, column) && !isNullish(data[column]) && !isNullish(stored[column]) && (columnTypes[column]?.type ?? "").toUpperCase() === "TIMESTAMP");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Indexed columns of `data` whose value provably equals the stored row's, in
|
|
165
|
+
* snake_case column names. `id` and `updated_at` are never reported: the
|
|
166
|
+
* revision token always advances, and `id` is the write's key.
|
|
167
|
+
*/
|
|
168
|
+
function unchangedIndexedColumns(data, stored, indexedColumns, columnTypes, exactTimestamps = {}) {
|
|
169
|
+
const unchanged = /* @__PURE__ */ new Set();
|
|
170
|
+
for (const [column, value] of Object.entries(data)) {
|
|
171
|
+
if (ALWAYS_ASSIGNED.has(column) || !indexedColumns.has(column)) continue;
|
|
172
|
+
if (!Object.hasOwn(stored, column)) continue;
|
|
173
|
+
if (storedValueEquals(columnTypes[column]?.type, value, stored[column], exactTimestamps[column])) unchanged.add(column);
|
|
174
|
+
}
|
|
175
|
+
return unchanged;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* An id-targeted `UPDATE` for native DuckDB, with every identifier quoted and
|
|
179
|
+
* every value bound the way the DuckDB adapter's `upsert()` binds its
|
|
180
|
+
* `DO UPDATE SET` values: `NULL` literals, `''` cast to TEXT, dates as ISO
|
|
181
|
+
* strings, arrays and plain objects as JSON text.
|
|
182
|
+
*
|
|
183
|
+
* The adapter's generic `update()` is not used because it renders column names
|
|
184
|
+
* unquoted (a column named `order` or `group` is a syntax error) and hands
|
|
185
|
+
* dates and structures to the driver untouched; the `upsert()` it replaces
|
|
186
|
+
* handled both.
|
|
187
|
+
*/
|
|
188
|
+
function buildDuckDbIdUpdate(table, id, assignments) {
|
|
189
|
+
const quote = (identifier) => `"${identifier.replaceAll("\"", "\"\"")}"`;
|
|
190
|
+
const values = [];
|
|
191
|
+
const sets = [];
|
|
192
|
+
for (const [column, value] of Object.entries(assignments)) {
|
|
193
|
+
let expression;
|
|
194
|
+
if (value === null || value === void 0) expression = "NULL";
|
|
195
|
+
else if (value === "") {
|
|
196
|
+
values.push(value);
|
|
197
|
+
expression = "CAST(? AS TEXT)";
|
|
198
|
+
} else if (value instanceof Date) {
|
|
199
|
+
values.push(value.toISOString());
|
|
200
|
+
expression = "?";
|
|
201
|
+
} else if (Array.isArray(value) || typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
202
|
+
values.push(JSON.stringify(value));
|
|
203
|
+
expression = "CAST(? AS JSON)";
|
|
204
|
+
} else {
|
|
205
|
+
values.push(value);
|
|
206
|
+
expression = "?";
|
|
207
|
+
}
|
|
208
|
+
sets.push(`${quote(column)} = ${expression}`);
|
|
209
|
+
}
|
|
210
|
+
values.push(id);
|
|
211
|
+
return {
|
|
212
|
+
sql: `UPDATE ${quote(table)} SET ${sets.join(", ")} WHERE ${quote("id")} = ?`,
|
|
213
|
+
values
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
export { buildDuckDbIdUpdate, collectIndexedColumns, jsonNumbersRoundTrip, storedValueEquals, timestampColumnsToProve, timestampMicros, unchangedIndexedColumns };
|
|
218
|
+
|
|
219
|
+
//# sourceMappingURL=unchanged-indexed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unchanged-indexed.js","names":[],"sources":["../src/unchanged-indexed.ts"],"sourcesContent":["/**\n * Unchanged indexed assignments on persisted rows (#3737).\n *\n * DuckDB rewrites a row (delete + insert) when an INDEXED column is assigned,\n * even with its existing value. A parent that a foreign key still references\n * therefore raises a referenced-parent violation for `SET slug = <same slug>`.\n * `SmrtObject.save()` serializes the whole model, so every existing-row write\n * used to assign `slug`, `context`, `tenant_id`, `created_at`, ... unchanged.\n *\n * This module holds the pure decisions: which columns are indexed, whether a\n * pending value provably equals the stored one, and how an assignment value\n * is bound by the generic UPDATE API. Nothing here touches a database, so the\n * save path keeps owning revision checks, tenant isolation and error typing.\n *\n * Everything errs toward \"assign as before\": an unknown column, a missing\n * stored value or a value that cannot be compared is NOT reported unchanged.\n */\n\nimport type { SchemaDefinition } from './schema/types.js';\nimport { toSnakeCase } from './utils';\n\n/** Columns the save path always writes, whatever their stored value. */\nconst ALWAYS_ASSIGNED = new Set(['id', 'updated_at']);\n\n/**\n * Columns whose assignment DuckDB may treat as an index rewrite.\n *\n * The union of what the registry schema declares (primary key, UNIQUE, every\n * declared index including partial and JSON-path ones, foreign keys) and the\n * natural-key / ownership columns the runtime itself keys writes by. Being\n * over-inclusive is harmless — a column is only skipped when it is also proven\n * unchanged — so this never has to be an exact model of the physical DDL.\n */\nexport function collectIndexedColumns(\n schemas: ReadonlyArray<SchemaDefinition | undefined>,\n runtimeKeyColumns: Iterable<string>,\n): Set<string> {\n const indexed = new Set<string>();\n for (const column of runtimeKeyColumns) {\n // Only camelCase field names need mapping; `toSnakeCase` would strip the\n // leading underscore of `_meta_type`.\n indexed.add(/[A-Z]/u.test(column) ? toSnakeCase(column) : column);\n }\n for (const schema of schemas) {\n if (!schema) continue;\n for (const [name, column] of Object.entries(schema.columns ?? {})) {\n if (column.primaryKey || column.unique || column.foreignKey) {\n indexed.add(name);\n }\n }\n for (const index of schema.indexes ?? []) {\n for (const name of index.columns ?? []) indexed.add(name);\n if (index.jsonPath?.column) indexed.add(index.jsonPath.column);\n }\n for (const foreignKey of schema.foreignKeys ?? []) {\n indexed.add(foreignKey.column);\n }\n }\n return indexed;\n}\n\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;\nconst TIMESTAMP_TEXT =\n /^(\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?\\s*(Z|[+-]\\d{2}(?::?\\d{2})?)?$/iu;\n\nfunction isNullish(value: unknown): value is null | undefined {\n return value === null || value === undefined;\n}\n\n/**\n * An instant as whole microseconds since the epoch, or `undefined` when it\n * cannot be established exactly.\n *\n * A `Date` holds milliseconds. Text is read at its full written precision;\n * digits beyond microseconds must be zero. Text without a zone is a wall\n * clock in the process zone unless `naiveIsUtc` (DuckDB's `TIMESTAMP` text,\n * which this runtime always writes as UTC).\n */\nexport function timestampMicros(\n value: unknown,\n naiveIsUtc = false,\n): bigint | undefined {\n if (value instanceof Date) {\n const time = value.getTime();\n return Number.isNaN(time) ? undefined : BigInt(time) * 1000n;\n }\n if (typeof value !== 'string') return undefined;\n const match = TIMESTAMP_TEXT.exec(value.trim());\n if (!match) return undefined;\n const [, year, month, day, hour, minute, second, fraction = '', zone] = match;\n if (fraction.length > 6 && /[1-9]/u.test(fraction.slice(6))) return undefined;\n if (zone === undefined && !naiveIsUtc) return undefined;\n // `Date.UTC` maps years 0-99 onto 1900-1999; set the literal year instead.\n const date = new Date(0);\n date.setUTCFullYear(Number(year), Number(month) - 1, Number(day));\n date.setUTCHours(Number(hour), Number(minute), Number(second), 0);\n const wall = date.getTime();\n if (Number.isNaN(wall)) return undefined;\n // Date rolls impossible fields over (month 13, minute 60, Feb 30); a field\n // that does not survive the round trip makes the text unprovable.\n if (\n date.getUTCFullYear() !== Number(year) ||\n date.getUTCMonth() !== Number(month) - 1 ||\n date.getUTCDate() !== Number(day) ||\n date.getUTCHours() !== Number(hour) ||\n date.getUTCMinutes() !== Number(minute) ||\n date.getUTCSeconds() !== Number(second)\n ) {\n return undefined;\n }\n let offsetMinutes = 0;\n if (zone && zone.toUpperCase() !== 'Z') {\n const digits = zone.slice(1).replace(':', '');\n const offsetHours = Number(digits.slice(0, 2));\n const offsetRest = Number(digits.slice(2) || '0');\n if (offsetHours > 23 || offsetRest > 59) return undefined;\n offsetMinutes =\n (zone.startsWith('-') ? -1 : 1) * (offsetHours * 60 + offsetRest);\n }\n const micros = BigInt(fraction.slice(0, 6).padEnd(6, '0'));\n return (BigInt(wall) - BigInt(offsetMinutes) * 60000n) * 1000n + micros;\n}\n\nconst JSON_NUMBER = /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y;\n\n/**\n * Whether every number literal in JSON text survives `JSON.parse` exactly.\n *\n * `JSON.parse` reads numbers as doubles, so `9007199254740993` and\n * `9007199254740992` parse to the same value. A literal is exact when the\n * double it parses to prints back as the same text; anything else (unsafe\n * integers, more digits than a double holds, `1.0`, `1e2`) makes the text\n * unprovable. Digits inside JSON strings are not numbers and are skipped.\n */\nexport function jsonNumbersRoundTrip(text: string): boolean {\n let inString = false;\n for (let index = 0; index < text.length; index += 1) {\n const char = text[index];\n if (inString) {\n if (char === '\\\\') index += 1;\n else if (char === '\"') inString = false;\n continue;\n }\n if (char === '\"') {\n inString = true;\n continue;\n }\n if (char === '-' || (char >= '0' && char <= '9')) {\n JSON_NUMBER.lastIndex = index;\n const match = JSON_NUMBER.exec(text);\n if (!match) return false;\n const literal = match[0];\n if (JSON.stringify(Number(literal)) !== literal) return false;\n index += literal.length - 1;\n }\n }\n return true;\n}\n\nfunction canonicalJson(value: unknown): string | undefined {\n let parsed = value;\n if (typeof value === 'string') {\n if (!jsonNumbersRoundTrip(value)) return undefined;\n try {\n parsed = JSON.parse(value);\n } catch {\n return undefined;\n }\n }\n const normalize = (node: unknown): unknown => {\n if (Array.isArray(node)) return node.map(normalize);\n if (node !== null && typeof node === 'object') {\n return Object.fromEntries(\n Object.entries(node as Record<string, unknown>)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([key, child]) => [key, normalize(child)]),\n );\n }\n return node;\n };\n try {\n return JSON.stringify(normalize(parsed));\n } catch {\n return undefined;\n }\n}\n\n/**\n * Whether `next` (the value about to be written) provably equals `stored`\n * (the canonical value read back from the row). `false` means \"assign it\":\n * either it differs or equality cannot be established.\n */\nexport function storedValueEquals(\n columnType: string | undefined,\n next: unknown,\n stored: unknown,\n exactStoredTimestamp?: string,\n): boolean {\n if (isNullish(next) || isNullish(stored)) {\n return isNullish(next) && isNullish(stored);\n }\n const type = (columnType ?? '').toUpperCase();\n if (type === 'TIMESTAMP') {\n // A hydrated `Date` has already lost anything below the millisecond, so\n // only the stored value as the database writes it can prove equality.\n if (exactStoredTimestamp === undefined) return false;\n const a = timestampMicros(next);\n const b = timestampMicros(exactStoredTimestamp, true);\n return a !== undefined && b !== undefined && a === b;\n }\n if (type === 'JSON') {\n const a = canonicalJson(next);\n const b = canonicalJson(stored);\n return a !== undefined && b !== undefined && a === b;\n }\n if (typeof next === 'string' && typeof stored === 'string') {\n if (\n type === 'UUID' &&\n UUID_PATTERN.test(next) &&\n UUID_PATTERN.test(stored)\n ) {\n return next.toLowerCase() === stored.toLowerCase();\n }\n return next === stored;\n }\n if (typeof next === 'boolean' && typeof stored === 'boolean') {\n return next === stored;\n }\n const numeric = (value: unknown): bigint | number | undefined =>\n typeof value === 'bigint' || typeof value === 'number' ? value : undefined;\n const a = numeric(next);\n const b = numeric(stored);\n if (a !== undefined && b !== undefined) {\n // biome-ignore lint/suspicious/noDoubleEquals: bigint/number cross-type value equality is the point.\n return a == b;\n }\n return false;\n}\n\n/**\n * `TIMESTAMP` columns of `data` whose equality needs the stored value at full\n * precision (see {@link storedValueEquals}); the caller reads them as text.\n */\nexport function timestampColumnsToProve(\n data: Readonly<Record<string, unknown>>,\n stored: Readonly<Record<string, unknown>>,\n indexedColumns: ReadonlySet<string>,\n columnTypes: Readonly<Record<string, { type?: string } | undefined>>,\n): string[] {\n return Object.keys(data).filter(\n (column) =>\n !ALWAYS_ASSIGNED.has(column) &&\n indexedColumns.has(column) &&\n Object.hasOwn(stored, column) &&\n !isNullish(data[column]) &&\n !isNullish(stored[column]) &&\n (columnTypes[column]?.type ?? '').toUpperCase() === 'TIMESTAMP',\n );\n}\n\n/**\n * Indexed columns of `data` whose value provably equals the stored row's, in\n * snake_case column names. `id` and `updated_at` are never reported: the\n * revision token always advances, and `id` is the write's key.\n */\nexport function unchangedIndexedColumns(\n data: Readonly<Record<string, unknown>>,\n stored: Readonly<Record<string, unknown>>,\n indexedColumns: ReadonlySet<string>,\n columnTypes: Readonly<Record<string, { type?: string } | undefined>>,\n exactTimestamps: Readonly<Record<string, string>> = {},\n): Set<string> {\n const unchanged = new Set<string>();\n for (const [column, value] of Object.entries(data)) {\n if (ALWAYS_ASSIGNED.has(column) || !indexedColumns.has(column)) continue;\n // A column the stored row does not carry cannot be compared.\n if (!Object.hasOwn(stored, column)) continue;\n if (\n storedValueEquals(\n columnTypes[column]?.type,\n value,\n stored[column],\n exactTimestamps[column],\n )\n ) {\n unchanged.add(column);\n }\n }\n return unchanged;\n}\n\n/**\n * An id-targeted `UPDATE` for native DuckDB, with every identifier quoted and\n * every value bound the way the DuckDB adapter's `upsert()` binds its\n * `DO UPDATE SET` values: `NULL` literals, `''` cast to TEXT, dates as ISO\n * strings, arrays and plain objects as JSON text.\n *\n * The adapter's generic `update()` is not used because it renders column names\n * unquoted (a column named `order` or `group` is a syntax error) and hands\n * dates and structures to the driver untouched; the `upsert()` it replaces\n * handled both.\n */\nexport function buildDuckDbIdUpdate(\n table: string,\n id: unknown,\n assignments: Readonly<Record<string, unknown>>,\n): { sql: string; values: unknown[] } {\n const quote = (identifier: string) => `\"${identifier.replaceAll('\"', '\"\"')}\"`;\n const values: unknown[] = [];\n const sets: string[] = [];\n for (const [column, value] of Object.entries(assignments)) {\n let expression: string;\n if (value === null || value === undefined) {\n expression = 'NULL';\n } else if (value === '') {\n values.push(value);\n expression = 'CAST(? AS TEXT)';\n } else if (value instanceof Date) {\n values.push(value.toISOString());\n expression = '?';\n } else if (\n Array.isArray(value) ||\n (typeof value === 'object' &&\n Object.getPrototypeOf(value) === Object.prototype)\n ) {\n values.push(JSON.stringify(value));\n expression = 'CAST(? AS JSON)';\n } else {\n values.push(value);\n expression = '?';\n }\n sets.push(`${quote(column)} = ${expression}`);\n }\n values.push(id);\n return {\n sql: `UPDATE ${quote(table)} SET ${sets.join(', ')} WHERE ${quote('id')} = ?`,\n values,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async-local state that survives `await`, without a static `node:async_hooks`
|
|
3
|
+
* import (#2838).
|
|
4
|
+
*
|
|
5
|
+
* A Node host backs it with `AsyncLocalStorage`, resolved through
|
|
6
|
+
* `process.getBuiltinModule()` so a browser bundle never reaches the built-in.
|
|
7
|
+
* Browsers have no equivalent that propagates through promises, so there
|
|
8
|
+
* `run()` only calls the callback, `getStore()` stays empty, and
|
|
9
|
+
* `propagatesAcrossAwait` is false. Callers whose correctness needs the
|
|
10
|
+
* store to reach nested async work (re-entrant locks) must check that flag
|
|
11
|
+
* and not rely on the store when it is false.
|
|
12
|
+
*/
|
|
13
|
+
export interface AsyncContext<T> {
|
|
14
|
+
/** Whether a value passed to `run()` reaches async work started inside it. */
|
|
15
|
+
readonly propagatesAcrossAwait: boolean;
|
|
16
|
+
/** Run `callback` with `store` visible to `getStore()` for its async extent. */
|
|
17
|
+
run<R>(store: T, callback: () => R): R;
|
|
18
|
+
/** The value of the innermost enclosing `run()`, if any. */
|
|
19
|
+
getStore(): T | undefined;
|
|
20
|
+
}
|
|
21
|
+
export declare function createAsyncContext<T>(): AsyncContext<T>;
|
|
22
|
+
//# sourceMappingURL=async-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-context.d.ts","sourceRoot":"","sources":["../../src/utils/async-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;IACxC,gFAAgF;IAChF,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,4DAA4D;IAC5D,QAAQ,IAAI,CAAC,GAAG,SAAS,CAAC;CAC3B;AAmBD,wBAAgB,kBAAkB,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,CAevD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/utils/async-context.ts
|
|
2
|
+
function resolveAsyncLocalStorage() {
|
|
3
|
+
if (typeof process === "undefined") return void 0;
|
|
4
|
+
const getBuiltinModule = process.getBuiltinModule;
|
|
5
|
+
if (typeof getBuiltinModule !== "function") return void 0;
|
|
6
|
+
return getBuiltinModule("node:async_hooks")?.AsyncLocalStorage;
|
|
7
|
+
}
|
|
8
|
+
function createAsyncContext() {
|
|
9
|
+
const Storage = resolveAsyncLocalStorage();
|
|
10
|
+
if (Storage) {
|
|
11
|
+
const storage = new Storage();
|
|
12
|
+
return {
|
|
13
|
+
propagatesAcrossAwait: true,
|
|
14
|
+
run: (store, callback) => storage.run(store, callback),
|
|
15
|
+
getStore: () => storage.getStore()
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
propagatesAcrossAwait: false,
|
|
20
|
+
run: (_store, callback) => callback(),
|
|
21
|
+
getStore: () => void 0
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { createAsyncContext };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=async-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-context.js","names":[],"sources":["../../src/utils/async-context.ts"],"sourcesContent":["/**\n * Async-local state that survives `await`, without a static `node:async_hooks`\n * import (#2838).\n *\n * A Node host backs it with `AsyncLocalStorage`, resolved through\n * `process.getBuiltinModule()` so a browser bundle never reaches the built-in.\n * Browsers have no equivalent that propagates through promises, so there\n * `run()` only calls the callback, `getStore()` stays empty, and\n * `propagatesAcrossAwait` is false. Callers whose correctness needs the\n * store to reach nested async work (re-entrant locks) must check that flag\n * and not rely on the store when it is false.\n */\n\nexport interface AsyncContext<T> {\n /** Whether a value passed to `run()` reaches async work started inside it. */\n readonly propagatesAcrossAwait: boolean;\n /** Run `callback` with `store` visible to `getStore()` for its async extent. */\n run<R>(store: T, callback: () => R): R;\n /** The value of the innermost enclosing `run()`, if any. */\n getStore(): T | undefined;\n}\n\ntype AsyncLocalStorageConstructor = new <T>() => {\n run<R>(store: T, callback: () => R): R;\n getStore(): T | undefined;\n};\n\nfunction resolveAsyncLocalStorage(): AsyncLocalStorageConstructor | undefined {\n if (typeof process === 'undefined') return undefined;\n const getBuiltinModule = (\n process as typeof process & { getBuiltinModule?: (id: string) => unknown }\n ).getBuiltinModule;\n if (typeof getBuiltinModule !== 'function') return undefined;\n const hooks = getBuiltinModule('node:async_hooks') as\n | { AsyncLocalStorage?: AsyncLocalStorageConstructor }\n | undefined;\n return hooks?.AsyncLocalStorage;\n}\n\nexport function createAsyncContext<T>(): AsyncContext<T> {\n const Storage = resolveAsyncLocalStorage();\n if (Storage) {\n const storage = new Storage<T>();\n return {\n propagatesAcrossAwait: true,\n run: (store, callback) => storage.run(store, callback),\n getStore: () => storage.getStore(),\n };\n }\n return {\n propagatesAcrossAwait: false,\n run: (_store, callback) => callback(),\n getStore: () => undefined,\n };\n}\n"],"mappings":";AA2BA,SAAS,2BAAqE;CAC5E,IAAI,OAAO,YAAY,aAAa,OAAO,KAAA;CAC3C,MAAM,mBACJ,QACA;CACF,IAAI,OAAO,qBAAqB,YAAY,OAAO,KAAA;CAInD,OAHc,iBAAiB,kBAGxB,CAAA,EAAO;AAChB;AAEA,SAAgB,qBAAyC;CACvD,MAAM,UAAU,yBAAyB;CACzC,IAAI,SAAS;EACX,MAAM,UAAU,IAAI,QAAW;EAC/B,OAAO;GACL,uBAAuB;GACvB,MAAM,OAAO,aAAa,QAAQ,IAAI,OAAO,QAAQ;GACrD,gBAAgB,QAAQ,SAAS;EACnC;CACF;CACA,OAAO;EACL,uBAAuB;EACvB,MAAM,QAAQ,aAAa,SAAS;EACpC,gBAAgB,KAAA;CAClB;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { dirname, join, resolve } from "node:path";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
//#region src/utils/import-project-vite.ts
|
|
6
6
|
/** Picks the ESM entry of a package `exports["."]` target (string or conditions). */
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser build of `json.ts` (#2838): selected through `package.json#browser`.
|
|
3
|
+
*
|
|
4
|
+
* `json.ts` asks `@happyvertical/json` for an adapter, and that package's
|
|
5
|
+
* SIMD adapter loader reaches `node:module`, `node:path` and `node:url`. A
|
|
6
|
+
* page has no SIMD adapter to pick, so this build is the native `JSON`
|
|
7
|
+
* implementation `json.ts` already falls back to. Typed against `json.ts`,
|
|
8
|
+
* so the two cannot drift.
|
|
9
|
+
*/
|
|
10
|
+
import type * as NodeJson from './json.js';
|
|
11
|
+
export declare const parse: typeof NodeJson.parse;
|
|
12
|
+
export declare const stringify: typeof NodeJson.stringify;
|
|
13
|
+
export declare const clone: typeof NodeJson.clone;
|
|
14
|
+
export declare const safeParse: typeof NodeJson.safeParse;
|
|
15
|
+
export declare const safeStringify: typeof NodeJson.safeStringify;
|
|
16
|
+
export declare const isValid: typeof NodeJson.isValid;
|
|
17
|
+
export declare const getAdapterInfo: typeof NodeJson.getAdapterInfo;
|
|
18
|
+
//# sourceMappingURL=json.browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.browser.d.ts","sourceRoot":"","sources":["../../src/utils/json.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,KAAK,QAAQ,MAAM,WAAW,CAAC;AAE3C,eAAO,MAAM,KAAK,EAAE,OAAO,QAAQ,CAAC,KACb,CAAC;AAExB,eAAO,MAAM,SAAS,EAAE,OAAO,QAAQ,CAAC,SAKrC,CAAC;AAEJ,eAAO,MAAM,KAAK,EAAE,OAAO,QAAQ,CAAC,KACI,CAAC;AAEzC,eAAO,MAAM,SAAS,EAAE,OAAO,QAAQ,CAAC,SAavC,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,OAAO,QAAQ,CAAC,aAW3C,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,OAAO,QAAQ,CAAC,OAOrC,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,OAAO,QAAQ,CAAC,cAI3C,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/utils/json.browser.ts
|
|
2
|
+
var parse = (text) => JSON.parse(text);
|
|
3
|
+
var stringify = (value, replacer, space) => JSON.stringify(value, replacer, space);
|
|
4
|
+
var clone = (value) => JSON.parse(JSON.stringify(value));
|
|
5
|
+
var safeParse = (text) => {
|
|
6
|
+
try {
|
|
7
|
+
return {
|
|
8
|
+
success: true,
|
|
9
|
+
value: JSON.parse(text)
|
|
10
|
+
};
|
|
11
|
+
} catch (error) {
|
|
12
|
+
return {
|
|
13
|
+
success: false,
|
|
14
|
+
error: { message: error instanceof Error ? error.message : String(error) }
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var safeStringify = (value) => {
|
|
19
|
+
try {
|
|
20
|
+
return {
|
|
21
|
+
success: true,
|
|
22
|
+
value: JSON.stringify(value)
|
|
23
|
+
};
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
error: { message: error instanceof Error ? error.message : String(error) }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var isValid = (text) => {
|
|
32
|
+
try {
|
|
33
|
+
JSON.parse(text);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var getAdapterInfo = () => ({
|
|
40
|
+
name: "native",
|
|
41
|
+
isNative: true,
|
|
42
|
+
simdEnabled: false
|
|
43
|
+
});
|
|
44
|
+
//#endregion
|
|
45
|
+
export { clone, getAdapterInfo, isValid, parse, safeParse, safeStringify, stringify };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=json.browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.browser.js","names":[],"sources":["../../src/utils/json.browser.ts"],"sourcesContent":["/**\n * Browser build of `json.ts` (#2838): selected through `package.json#browser`.\n *\n * `json.ts` asks `@happyvertical/json` for an adapter, and that package's\n * SIMD adapter loader reaches `node:module`, `node:path` and `node:url`. A\n * page has no SIMD adapter to pick, so this build is the native `JSON`\n * implementation `json.ts` already falls back to. Typed against `json.ts`,\n * so the two cannot drift.\n */\nimport type * as NodeJson from './json.js';\n\nexport const parse: typeof NodeJson.parse = <T = unknown>(text: string): T =>\n JSON.parse(text) as T;\n\nexport const stringify: typeof NodeJson.stringify = (value, replacer, space) =>\n JSON.stringify(\n value,\n replacer as Parameters<typeof JSON.stringify>[1],\n space,\n );\n\nexport const clone: typeof NodeJson.clone = <T>(value: T): T =>\n JSON.parse(JSON.stringify(value)) as T;\n\nexport const safeParse: typeof NodeJson.safeParse = <T = unknown>(\n text: string,\n) => {\n try {\n return { success: true, value: JSON.parse(text) as T } as const;\n } catch (error) {\n return {\n success: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n },\n } as const;\n }\n};\n\nexport const safeStringify: typeof NodeJson.safeStringify = (value) => {\n try {\n return { success: true, value: JSON.stringify(value) } as const;\n } catch (error) {\n return {\n success: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n },\n } as const;\n }\n};\n\nexport const isValid: typeof NodeJson.isValid = (text) => {\n try {\n JSON.parse(text);\n return true;\n } catch {\n return false;\n }\n};\n\nexport const getAdapterInfo: typeof NodeJson.getAdapterInfo = () => ({\n name: 'native',\n isNative: true,\n simdEnabled: false,\n});\n"],"mappings":";AAWA,IAAa,SAA6C,SACxD,KAAK,MAAM,IAAI;AAEjB,IAAa,aAAwC,OAAO,UAAU,UACpE,KAAK,UACH,OACA,UACA,KACF;AAEF,IAAa,SAAmC,UAC9C,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAElC,IAAa,aACX,SACG;CACH,IAAI;EACF,OAAO;GAAE,SAAS;GAAM,OAAO,KAAK,MAAM,IAAI;EAAO;CACvD,SAAS,OAAO;EACd,OAAO;GACL,SAAS;GACT,OAAO,EACL,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAChE;EACF;CACF;AACF;AAEA,IAAa,iBAAgD,UAAU;CACrE,IAAI;EACF,OAAO;GAAE,SAAS;GAAM,OAAO,KAAK,UAAU,KAAK;EAAE;CACvD,SAAS,OAAO;EACd,OAAO;GACL,SAAS;GACT,OAAO,EACL,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAChE;EACF;CACF;AACF;AAEA,IAAa,WAAoC,SAAS;CACxD,IAAI;EACF,KAAK,MAAM,IAAI;EACf,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,IAAa,wBAAwD;CACnE,MAAM;CACN,UAAU;CACV,aAAa;AACf"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256 of `text` (UTF-8) as lowercase hex.
|
|
3
|
+
*
|
|
4
|
+
* Synchronous and free of `node:crypto`, so the schema, registry and
|
|
5
|
+
* embedding paths that fingerprint strings load in a browser (#2838). The
|
|
6
|
+
* digest is identical to `createHash('sha256').update(text).digest('hex')`;
|
|
7
|
+
* `sha256.test.ts` pins that against Node's implementation.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sha256Hex(text: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* SHA-256 of `text` (UTF-8) as unpadded base64url, identical to
|
|
12
|
+
* `createHash('sha256').update(text).digest('base64url')`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sha256Base64Url(text: string): string;
|
|
15
|
+
//# sourceMappingURL=sha256.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sha256.d.ts","sourceRoot":"","sources":["../../src/utils/sha256.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQpD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { bytesToHex } from "../node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/utils.js";
|
|
2
|
+
import { sha256 } from "../node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/sha2.js";
|
|
3
|
+
//#region src/utils/sha256.ts
|
|
4
|
+
var encoder = new TextEncoder();
|
|
5
|
+
/**
|
|
6
|
+
* SHA-256 of `text` (UTF-8) as lowercase hex.
|
|
7
|
+
*
|
|
8
|
+
* Synchronous and free of `node:crypto`, so the schema, registry and
|
|
9
|
+
* embedding paths that fingerprint strings load in a browser (#2838). The
|
|
10
|
+
* digest is identical to `createHash('sha256').update(text).digest('hex')`;
|
|
11
|
+
* `sha256.test.ts` pins that against Node's implementation.
|
|
12
|
+
*/
|
|
13
|
+
function sha256Hex(text) {
|
|
14
|
+
return bytesToHex(sha256(encoder.encode(text)));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* SHA-256 of `text` (UTF-8) as unpadded base64url, identical to
|
|
18
|
+
* `createHash('sha256').update(text).digest('base64url')`.
|
|
19
|
+
*/
|
|
20
|
+
function sha256Base64Url(text) {
|
|
21
|
+
const bytes = sha256(encoder.encode(text));
|
|
22
|
+
let binary = "";
|
|
23
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
24
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { sha256Base64Url, sha256Hex };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=sha256.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sha256.js","names":[],"sources":["../../src/utils/sha256.ts"],"sourcesContent":["import { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\n\nconst encoder = new TextEncoder();\n\n/**\n * SHA-256 of `text` (UTF-8) as lowercase hex.\n *\n * Synchronous and free of `node:crypto`, so the schema, registry and\n * embedding paths that fingerprint strings load in a browser (#2838). The\n * digest is identical to `createHash('sha256').update(text).digest('hex')`;\n * `sha256.test.ts` pins that against Node's implementation.\n */\nexport function sha256Hex(text: string): string {\n return bytesToHex(sha256(encoder.encode(text)));\n}\n\n/**\n * SHA-256 of `text` (UTF-8) as unpadded base64url, identical to\n * `createHash('sha256').update(text).digest('base64url')`.\n */\nexport function sha256Base64Url(text: string): string {\n const bytes = sha256(encoder.encode(text));\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n"],"mappings":";;;AAGA,IAAM,UAAU,IAAI,YAAY;;;;;;;;;AAUhC,SAAgB,UAAU,MAAsB;CAC9C,OAAO,WAAW,OAAO,QAAQ,OAAO,IAAI,CAAC,CAAC;AAChD;;;;;AAMA,SAAgB,gBAAgB,MAAsB;CACpD,MAAM,QAAQ,OAAO,QAAQ,OAAO,IAAI,CAAC;CACzC,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,OAAO,UAAU,OAAO,aAAa,IAAI;CAC5D,OAAO,KAAK,MAAM,CAAC,CAChB,QAAQ,OAAO,GAAG,CAAC,CACnB,QAAQ,OAAO,GAAG,CAAC,CACnB,QAAQ,OAAO,EAAE;AACtB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AUTO_GENERATED_ROUTE_HEADER } from "./route-header.js";
|
|
2
2
|
import { generateCollectionAccessImports } from "./sveltekit-config-import.js";
|
|
3
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
3
|
import { join } from "node:path";
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
//#region src/vite-plugin/changes-route.ts
|
|
6
6
|
/**
|
|
7
7
|
* SvelteKit `_changes` route generation for the change feed (issue #1758).
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AUTO_GENERATED_ROUTE_HEADER } from "./route-header.js";
|
|
2
2
|
import { generateCollectionAccessImports } from "./sveltekit-config-import.js";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
|
-
import { join } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
//#region src/vite-plugin/dev-plane-route.ts
|
|
8
8
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AUTO_GENERATED_ROUTE_HEADER } from "./route-header.js";
|
|
2
2
|
import { generateCollectionAccessImports } from "./sveltekit-config-import.js";
|
|
3
3
|
import { manifestHasTenantScopedObject, resolveAnchorClassName } from "./changes-route.js";
|
|
4
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
4
|
import { join } from "node:path";
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
6
6
|
//#region src/vite-plugin/events-route.ts
|
|
7
7
|
/**
|
|
8
8
|
* SvelteKit `_events` route generation — live change signals (issue #1763,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { dirname, extname, resolve } from "node:path";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
|
-
import { dirname, extname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
//#region src/vite-plugin/import-build-aware.ts
|
|
6
6
|
/**
|