@absolutejs/absolute 0.20.0-beta.120 → 0.20.0-beta.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/changelog.json +21 -0
  3. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  4. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  5. package/dist/build.js +2 -2
  6. package/dist/build.js.map +3 -3
  7. package/dist/cli/{add-kt4qx8sh.js → add-9sa7e3vd.js} +5 -5
  8. package/dist/cli/{androidEmulatorController-v6chbhem.js → androidEmulatorController-sj141e1c.js} +3 -3
  9. package/dist/cli/{androidNativeWatcher-mgqws6ag.js → androidNativeWatcher-dnp95nk2.js} +3 -3
  10. package/dist/cli/{api-xtq3zqfg.js → api-2hd8cahp.js} +1 -1
  11. package/dist/cli/{compile-5m830csy.js → compile-hywymh6y.js} +1 -1
  12. package/dist/cli/config/server.js +50 -8
  13. package/dist/cli/db-8wweexdp.js +1100 -0
  14. package/dist/cli/{dev-ww30tdrv.js → dev-pdefc9ac.js} +9 -9
  15. package/dist/cli/{emulatorDoctor-y2nqmaha.js → emulatorDoctor-gzarrsex.js} +2 -2
  16. package/dist/cli/{emulatorInstaller-3shdna4g.js → emulatorInstaller-pjw4ex6q.js} +3 -3
  17. package/dist/cli/{expoDevController-zz516pdd.js → expoDevController-weywa74d.js} +3 -3
  18. package/dist/cli/{generate-zy3d9bce.js → generate-069p0dk3.js} +4 -4
  19. package/dist/cli/{htmx-63kt9qj1.js → htmx-5wh1jh75.js} +2 -2
  20. package/dist/cli/{index-t84wxk1p.js → index-00wfffav.js} +50 -8
  21. package/dist/cli/{index-wtyg9cy0.js → index-1ys3dnrs.js} +1 -1
  22. package/dist/cli/{index-knt92dm8.js → index-255xs63d.js} +1 -1
  23. package/dist/cli/{index-wspj2mq3.js → index-3x5wpx5d.js} +1 -1
  24. package/dist/cli/{index-28nxv57v.js → index-5cgsrv62.js} +12 -2
  25. package/dist/cli/{index-mxmg15c5.js → index-ddakkws4.js} +2 -2
  26. package/dist/cli/{index-ce870r79.js → index-erngp7a9.js} +1 -1
  27. package/dist/cli/{index-pa3hmzj7.js → index-j2nw3edf.js} +1 -1
  28. package/dist/cli/{index-1wgc9e0v.js → index-keg69zm1.js} +1 -1
  29. package/dist/cli/{index-5268nwjx.js → index-q3sq287p.js} +1 -1
  30. package/dist/cli/{index-jf1f6npt.js → index-rqbk0b30.js} +1 -1
  31. package/dist/cli/{index-q1d4ns4n.js → index-v26ze17p.js} +1 -1
  32. package/dist/cli/{index-hneb973j.js → index-vkkqe08e.js} +2 -2
  33. package/dist/cli/index.js +23 -18
  34. package/dist/cli/{info-crpftzxh.js → info-d74dr585.js} +1 -1
  35. package/dist/cli/{iosNativeWatcher-8w1gawg4.js → iosNativeWatcher-kggp7j9a.js} +4 -4
  36. package/dist/cli/{iosSimulatorController-5vhqvhg6.js → iosSimulatorController-xcxxfkzx.js} +4 -4
  37. package/dist/cli/{mobile-21vsmrjd.js → mobile-00bqw12c.js} +9 -9
  38. package/dist/cli/{ps-ha7bhpc6.js → ps-8y28kzmb.js} +1 -1
  39. package/dist/cli/{psTui-rxszyyz8.js → psTui-8e96mwgz.js} +1 -1
  40. package/dist/cli/{remoteMacProtocol-dybpgazw.js → remoteMacProtocol-r38jhg44.js} +4 -4
  41. package/dist/cli/{remove-pwfv4wsd.js → remove-3fd1z2hx.js} +4 -4
  42. package/dist/cli/{start-3bhf56tj.js → start-q08avhxg.js} +2 -2
  43. package/dist/cli/{typecheck-7n5599tp.js → typecheck-yj9s13eb.js} +1 -1
  44. package/dist/cli/{workspace-h7czpaqh.js → workspace-e3jt4as8.js} +1 -1
  45. package/dist/index.js +2 -2
  46. package/dist/index.js.map +3 -3
  47. package/dist/mobile/index.js +13 -3
  48. package/dist/mobile/index.js.map +4 -4
  49. package/dist/mobile/remoteMacAgentEntry.js +162 -162
  50. package/dist/src/cli/scripts/db.d.ts +2 -19
  51. package/dist/src/cli/scripts/dbEngines/detect.d.ts +8 -0
  52. package/dist/src/cli/scripts/dbEngines/mssql.d.ts +4 -0
  53. package/dist/src/cli/scripts/dbEngines/mysql.d.ts +2 -0
  54. package/dist/src/cli/scripts/dbEngines/open.d.ts +2 -0
  55. package/dist/src/cli/scripts/dbEngines/postgres.d.ts +2 -0
  56. package/dist/src/cli/scripts/dbEngines/sqlText.d.ts +13 -0
  57. package/dist/src/cli/scripts/dbEngines/sqlite.d.ts +4 -0
  58. package/dist/src/cli/scripts/dbEngines/values.d.ts +12 -0
  59. package/dist/src/cli/utils.d.ts +7 -0
  60. package/dist/src/utils/userAgentFunctions.d.ts +1 -1
  61. package/dist/types/db.d.ts +109 -0
  62. package/package.json +13 -1
  63. package/dist/cli/db-tejnwt9j.js +0 -238
@@ -1,20 +1,3 @@
1
- type ColumnMeta = {
2
- isJson: boolean;
3
- name: string;
4
- };
5
- type TableMeta = {
6
- columns: ColumnMeta[];
7
- name: string;
8
- primaryKey: string[];
9
- };
10
- type ForeignLink = {
11
- from: string;
12
- to: string;
13
- };
14
- export declare const chunkRows: <Item>(items: Item[], size: number) => Item[][];
15
- export declare const quoteIdent: (name: string) => string;
16
- export declare const conflictClause: (meta: TableMeta) => string;
17
- export declare const dependencyOrder: (names: string[], links: ForeignLink[]) => string[];
18
- export declare const encodeValue: (col: ColumnMeta, value: unknown) => {} | null;
1
+ export { chunkRows, conflictClause, dependencyOrder, quoteIdent } from './dbEngines/sqlText';
2
+ export { encodeValue } from './dbEngines/values';
19
3
  export declare const runDb: (args: string[]) => Promise<void>;
20
- export {};
@@ -0,0 +1,8 @@
1
+ import type { DbTarget, OrmDialectHint } from '../../../../types/db';
2
+ export declare const SUPPORTED_SCHEMES = "postgres://, postgresql://, cockroachdb://, mysql://, mariadb://, singlestore://, file:, sqlite:, libsql://, http(s)://, ws(s)://, sqlserver://, mssql://";
3
+ export declare const readOrmDialect: (cwd: string) => {
4
+ dialect: string;
5
+ source: string;
6
+ } | undefined;
7
+ export declare const detectTarget: (url: string, hint: OrmDialectHint | undefined) => DbTarget;
8
+ export declare const sqlitePath: (url: string, cwd: string) => string;
@@ -0,0 +1,4 @@
1
+ import type { DbConnection, MssqlColumnRow, MssqlTarget } from '../../../../types/db';
2
+ export declare const mssqlColumnType: (row: MssqlColumnRow) => string;
3
+ export declare const parseMssqlUrl: (url: string) => MssqlTarget;
4
+ export declare const openMssql: (url: string) => Promise<DbConnection>;
@@ -0,0 +1,2 @@
1
+ import type { DbConnection } from '../../../../types/db';
2
+ export declare const openMysql: (url: string) => Promise<DbConnection>;
@@ -0,0 +1,2 @@
1
+ import type { DbTarget } from '../../../../types/db';
2
+ export declare const openConnection: (target: DbTarget, cwd: string) => Promise<import("../../../../types/db").DbConnection>;
@@ -0,0 +1,2 @@
1
+ import type { DbConnection } from '../../../../types/db';
2
+ export declare const openPostgres: (url: string) => Promise<DbConnection>;
@@ -0,0 +1,13 @@
1
+ import type { DbColumnMeta, DbForeignLink, DbTableMeta } from '../../../../types/db';
2
+ export declare const chunkRows: <Item>(items: Item[], size: number) => Item[][];
3
+ export declare const insertableColumns: (meta: DbTableMeta) => DbColumnMeta[];
4
+ export declare const quoteIdent: (name: string) => string;
5
+ export declare const quoteMssqlIdent: (name: string) => string;
6
+ export declare const quoteMysqlIdent: (name: string) => string;
7
+ export declare const rowsPerChunk: (columnCount: number, paramLimit: number) => number;
8
+ export declare const conflictClause: (meta: DbTableMeta) => string;
9
+ export declare const mysqlInsert: (meta: DbTableMeta, rowCount: number, rowAlias: boolean) => string;
10
+ export declare const postgresInsert: (meta: DbTableMeta, rowCount: number, overridingSystemValue: boolean) => string;
11
+ export declare const sqliteInsert: (meta: DbTableMeta, rowCount: number) => string;
12
+ export declare const dependencyOrder: (names: string[], links: DbForeignLink[]) => string[];
13
+ export declare const mssqlUpsert: (meta: DbTableMeta, rowCount: number, qualifiedTable: string) => string;
@@ -0,0 +1,4 @@
1
+ import type { DbConnection, DbEngine, SqliteExecutor } from '../../../../types/db';
2
+ export declare const bunSqliteExecutor: (path: string) => SqliteExecutor;
3
+ export declare const libsqlExecutor: (url: string) => Promise<SqliteExecutor>;
4
+ export declare const openSqlite: (exec: SqliteExecutor, engine: DbEngine) => DbConnection;
@@ -0,0 +1,12 @@
1
+ import type { DbColumnMeta, DbRow } from '../../../../types/db';
2
+ export declare const decodeBinary: (value: unknown) => Buffer<ArrayBuffer> | undefined;
3
+ export declare const parseJsonText: (value: unknown) => unknown;
4
+ export declare const encodeMssqlValue: (col: DbColumnMeta, value: unknown) => {} | null;
5
+ export declare const encodeMysqlValue: (col: DbColumnMeta, value: unknown) => {} | null;
6
+ export declare const encodeSqliteValue: (col: DbColumnMeta, value: unknown) => {} | null;
7
+ export declare const encodeValue: (col: DbColumnMeta, value: unknown) => {} | null;
8
+ export declare const isoToSqlDateTime: (value: string) => string;
9
+ export declare const pgArrayLiteral: (values: unknown[]) => string;
10
+ export declare const normalizeRow: (row: DbRow) => {
11
+ [k: string]: unknown;
12
+ };
@@ -3,6 +3,13 @@ export declare const COMPOSE_PATH = "db/docker-compose.db.yml";
3
3
  export declare const DEFAULT_SERVER_ENTRY = "src/backend/server.ts";
4
4
  export declare const isWSLEnvironment: () => boolean;
5
5
  export declare const findFreePort: () => Promise<number>;
6
+ /**
7
+ * Whether a process holding the port is a leftover of this project's own
8
+ * AbsoluteJS server, and so safe to replace. Anything else on the port -- an
9
+ * unrelated service, or another project's server -- is someone else's and is
10
+ * left running.
11
+ */
12
+ export declare const isOwnStaleProcess: (command: string, directory: string | null, projectDirectory: string) => boolean;
6
13
  export declare const killStaleProcesses: (port: number, logMessage?: (message: string) => void) => void;
7
14
  export declare const openUrlInBrowser: (url: string, onError?: (message: string) => void) => boolean;
8
15
  export declare const printHelp: (subject?: string, mobile?: boolean) => void;
@@ -1 +1 @@
1
- export declare const getUserAgentType: (userAgent: string) => "console" | "phone" | "other" | "tablet" | "desktop" | "bot" | "tv" | "car" | "iot";
1
+ export declare const getUserAgentType: (userAgent: string) => "other" | "console" | "phone" | "tablet" | "desktop" | "bot" | "tv" | "car" | "iot";
@@ -0,0 +1,109 @@
1
+ export type DbFamily = 'libsql' | 'mssql' | 'mysql' | 'postgres' | 'sqlite';
2
+ export type DbEngine = 'cockroach' | 'libsql' | 'mariadb' | 'mssql' | 'mysql' | 'postgres' | 'singlestore' | 'sqlite';
3
+ export type DbColumnKind = 'array' | 'binary' | 'json' | 'other' | 'temporal';
4
+ export type DbColumnMeta = {
5
+ generated?: boolean;
6
+ identity?: boolean;
7
+ isJson: boolean;
8
+ kind?: DbColumnKind;
9
+ name: string;
10
+ sqlType?: string;
11
+ };
12
+ export type DbTableMeta = {
13
+ columns: DbColumnMeta[];
14
+ name: string;
15
+ primaryKey: string[];
16
+ };
17
+ export type DbForeignLink = {
18
+ from: string;
19
+ to: string;
20
+ };
21
+ export type DbRow = Record<string, unknown>;
22
+ export type DbBackupFile = {
23
+ at: string;
24
+ engine?: DbEngine;
25
+ tables: Record<string, DbRow[]>;
26
+ v: number;
27
+ };
28
+ export type DbOptions = {
29
+ exclude: string[];
30
+ only: string[];
31
+ out?: string;
32
+ truncate: boolean;
33
+ url: string;
34
+ yes: boolean;
35
+ };
36
+ export type DbConnection = {
37
+ afterRestore: (restored: DbTableMeta[]) => Promise<void>;
38
+ close: () => Promise<void>;
39
+ engine: DbEngine;
40
+ foreignLinks: () => Promise<DbForeignLink[]>;
41
+ insertRows: (meta: DbTableMeta, rows: DbRow[]) => Promise<void>;
42
+ listTables: () => Promise<string[]>;
43
+ readRows: (meta: DbTableMeta) => Promise<DbRow[]>;
44
+ tableMeta: (name: string) => Promise<DbTableMeta>;
45
+ truncate: (names: string[]) => Promise<void>;
46
+ };
47
+ export type DbTarget = {
48
+ family: DbFamily;
49
+ url: string;
50
+ };
51
+ export type OrmDialectHint = {
52
+ dialect: string;
53
+ source: string;
54
+ };
55
+ export type MysqlConnectOptions = {
56
+ allowPublicKeyRetrieval: boolean;
57
+ url: string;
58
+ };
59
+ export type MssqlTarget = {
60
+ database?: string;
61
+ encrypt: boolean;
62
+ instanceName?: string;
63
+ password?: string;
64
+ port?: number;
65
+ server: string;
66
+ trustServerCertificate: boolean;
67
+ user?: string;
68
+ };
69
+ export type SqliteExecutor = {
70
+ all: (query: string, params: unknown[]) => Promise<DbRow[]>;
71
+ batch: (statements: string[]) => Promise<void>;
72
+ close: () => Promise<void>;
73
+ run: (query: string, params: unknown[]) => Promise<void>;
74
+ };
75
+ export type PgColumnRow = {
76
+ column_default: string | null;
77
+ column_name: string;
78
+ data_type: string;
79
+ identity_generation: string | null;
80
+ is_generated: string | null;
81
+ is_identity: string | null;
82
+ };
83
+ export type MysqlColumnRow = {
84
+ data_type: string;
85
+ extra: string | null;
86
+ name: string;
87
+ };
88
+ export type SqliteColumnRow = {
89
+ hidden: unknown;
90
+ name: string;
91
+ pk: unknown;
92
+ type: string | null;
93
+ };
94
+ export type MssqlColumnRow = {
95
+ is_computed: boolean;
96
+ is_identity: boolean;
97
+ max_length: number;
98
+ name: string;
99
+ precision: number;
100
+ scale: number;
101
+ type: string;
102
+ };
103
+ export type NamedRow = {
104
+ name: string;
105
+ };
106
+ export type LinkRow = {
107
+ child: string;
108
+ parent: string;
109
+ };
package/package.json CHANGED
@@ -76,12 +76,14 @@
76
76
  "@glimmer/component": "^2.1.1",
77
77
  "@glimmer/tracking": "^1.1.2",
78
78
  "@happy-dom/global-registrator": "^20.8.3",
79
+ "@libsql/client": "0.18.0",
79
80
  "@simple-dom/document": "^1.4.0",
80
81
  "@simple-dom/serializer": "^1.4.0",
81
82
  "@simple-dom/void-map": "^1.4.0",
82
83
  "@sinclair/typebox": "0.34.52",
83
84
  "@stylistic/eslint-plugin": "^5.10.0",
84
85
  "@types/bun": "1.3.14",
86
+ "@types/mssql": "12.3.0",
85
87
  "@types/react": "19.2.0",
86
88
  "@types/react-dom": "19.2.0",
87
89
  "@vue/compiler-sfc": "3.5.27",
@@ -98,6 +100,7 @@
98
100
  "happy-dom": "^20.8.3",
99
101
  "jsonc-parser": "^3.3.1",
100
102
  "less": "4.6.4",
103
+ "mssql": "12.7.2",
101
104
  "playwright": "^1.59.1",
102
105
  "postcss": "8.5.3",
103
106
  "prettier": "3.5.3",
@@ -331,9 +334,11 @@
331
334
  "@capacitor/cli": "^8.5.0",
332
335
  "@capacitor/core": "^8.5.0",
333
336
  "@capacitor/ios": "^8.5.0",
337
+ "@libsql/client": ">=0.15.0 <1",
334
338
  "@vue/compiler-sfc": "^3.5.27",
335
339
  "elysia": "^2.0.0-beta.6",
336
340
  "less": "^4.6.0",
341
+ "mssql": ">=11.0.0 <13",
337
342
  "postcss": "^8.5.0",
338
343
  "react": "^19.2.0",
339
344
  "react-dom": "^19.2.0",
@@ -394,12 +399,18 @@
394
399
  "@capacitor/ios": {
395
400
  "optional": true
396
401
  },
402
+ "@libsql/client": {
403
+ "optional": true
404
+ },
397
405
  "@vue/compiler-sfc": {
398
406
  "optional": true
399
407
  },
400
408
  "less": {
401
409
  "optional": true
402
410
  },
411
+ "mssql": {
412
+ "optional": true
413
+ },
403
414
  "postcss": {
404
415
  "optional": true
405
416
  },
@@ -463,6 +474,7 @@
463
474
  "release:gate": "bun run scripts/releaseGate.ts",
464
475
  "start": "TELEMETRY_OFF=1 bun run src/cli/index.ts start example/server.ts --outdir example/dist --config example/absolute.config.ts",
465
476
  "test": "bun run test:unit && bun run test:integration",
477
+ "test:db": "bun test tests/integration/cli/db-engines.test.ts",
466
478
  "test:hmr": "bun run scripts/shardedTests.ts tests/integration/hmr",
467
479
  "test:integration": "bun run scripts/shardedTests.ts tests/integration",
468
480
  "test:mobile:update:durable": "ABSOLUTE_TEST_MOBILE_UPDATE_DURABLE=1 bun test tests/integration/mobile-update-durable-conformance.test.ts",
@@ -542,7 +554,7 @@
542
554
  ]
543
555
  }
544
556
  },
545
- "version": "0.20.0-beta.120",
557
+ "version": "0.20.0-beta.121",
546
558
  "workspaces": [
547
559
  "tests/fixtures/*",
548
560
  "tests/fixtures/_packages/*"
@@ -1,238 +0,0 @@
1
- // @bun
2
- import {
3
- colors
4
- } from "./index-8nym68py.js";
5
- import {
6
- UNFOUND_INDEX
7
- } from "./index-czwrxgdj.js";
8
- import"./index-mywsk201.js";
9
-
10
- // src/cli/scripts/db.ts
11
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
12
- import { join } from "path";
13
- var {env, spawn, SQL } = globalThis.Bun;
14
- var BACKUP_FORMAT_VERSION = 1;
15
- var RESTORE_CHUNK_ROWS = 500;
16
- var URL_ENV_KEYS = ["DATABASE_URL", "POSTGRES_URL", "DATABASE_URL_UNPOOLED"];
17
- var JSON_DATA_TYPES = ["json", "jsonb"];
18
- var SEED_CANDIDATES = ["db/seed.ts", "src/db/seed.ts", "seed.ts"];
19
- var VALUE_FLAGS = ["--out", "--url", "--only", "--exclude"];
20
- var paint = (text, color) => `${color}${text}${colors.reset}`;
21
- var chunkRows = (items, size) => Array.from({ length: Math.ceil(items.length / size) }, (_, idx) => items.slice(idx * size, idx * size + size));
22
- var quoteIdent = (name) => `"${name.replace(/"/g, '""')}"`;
23
- var resolveUrl = (explicit) => {
24
- const found = explicit ?? URL_ENV_KEYS.map((key) => env[key]).find((value) => typeof value === "string" && value !== "");
25
- if (found === undefined || found === "")
26
- throw new Error(`No database URL found. Set ${URL_ENV_KEYS.join(" or ")}, or pass --url <url>.`);
27
- return found;
28
- };
29
- var keepTable = (name, options) => (options.only.length === 0 || options.only.includes(name)) && !options.exclude.includes(name);
30
- var listTables = async (sql) => {
31
- const rows = await sql`select table_name from information_schema.tables where table_schema = ${"public"} and table_type = ${"BASE TABLE"} order by table_name`;
32
- return rows.map((row) => row.table_name);
33
- };
34
- var columnsFor = async (sql, name) => {
35
- const rows = await sql.unsafe(`select column_name, data_type from information_schema.columns where table_schema = $1 and table_name = $2 order by ordinal_position`, ["public", name]);
36
- return rows.map((row) => ({
37
- isJson: JSON_DATA_TYPES.includes(row.data_type),
38
- name: row.column_name
39
- }));
40
- };
41
- var primaryKeyFor = async (sql, name) => {
42
- const rows = await sql.unsafe(`select a.attname as col from pg_index i join pg_attribute a on a.attrelid = i.indrelid and a.attnum = any(i.indkey) where i.indrelid = $1::regclass and i.indisprimary order by a.attnum`, [`public.${quoteIdent(name)}`]);
43
- return rows.map((row) => row.col);
44
- };
45
- var tableMeta = async (sql, name) => {
46
- const [columns, primaryKey] = await Promise.all([
47
- columnsFor(sql, name),
48
- primaryKeyFor(sql, name)
49
- ]);
50
- return { columns, name, primaryKey };
51
- };
52
- var foreignLinks = async (sql) => {
53
- const rows = await sql.unsafe(`select tc.table_name as child, ccu.table_name as parent from information_schema.table_constraints tc join information_schema.constraint_column_usage ccu on ccu.constraint_name = tc.constraint_name and ccu.table_schema = tc.table_schema where tc.constraint_type = 'FOREIGN KEY' and tc.table_schema = $1`, ["public"]);
54
- return rows.map((row) => ({
55
- from: row.child,
56
- to: row.parent
57
- }));
58
- };
59
- var conflictClause = (meta) => {
60
- if (meta.primaryKey.length === 0)
61
- return "on conflict do nothing";
62
- const target = meta.primaryKey.map(quoteIdent).join(", ");
63
- const updatable = meta.columns.map((col) => col.name).filter((name) => !meta.primaryKey.includes(name));
64
- if (updatable.length === 0)
65
- return `on conflict (${target}) do nothing`;
66
- const sets = updatable.map((name) => `${quoteIdent(name)} = excluded.${quoteIdent(name)}`).join(", ");
67
- return `on conflict (${target}) do update set ${sets}`;
68
- };
69
- var dependencyOrder = (names, links) => {
70
- const present = new Set(names);
71
- const edges = links.filter((link) => present.has(link.from) && present.has(link.to) && link.from !== link.to);
72
- const indegree = new Map(names.map((name) => [name, 0]));
73
- edges.forEach((link) => indegree.set(link.from, (indegree.get(link.from) ?? 0) + 1));
74
- const ready = names.filter((name) => (indegree.get(name) ?? 0) === 0);
75
- const ordered = [];
76
- const release = (parent) => edges.filter((link) => link.to === parent).forEach((link) => {
77
- const next = (indegree.get(link.from) ?? 0) - 1;
78
- indegree.set(link.from, next);
79
- if (next === 0)
80
- ready.push(link.from);
81
- });
82
- const drain = () => {
83
- const head = ready.shift();
84
- if (head === undefined)
85
- return;
86
- ordered.push(head);
87
- release(head);
88
- drain();
89
- };
90
- drain();
91
- names.forEach((name) => {
92
- if (!ordered.includes(name))
93
- ordered.push(name);
94
- });
95
- return ordered;
96
- };
97
- var encodeValue = (col, value) => {
98
- if (value === null || value === undefined)
99
- return null;
100
- if (col.isJson)
101
- return JSON.stringify(value);
102
- return value;
103
- };
104
- var insertChunk = async (sql, meta, rows) => {
105
- const colNames = meta.columns.map((col) => col.name);
106
- const groups = rows.map((_, rowIdx) => {
107
- const base = rowIdx * colNames.length;
108
- const slots = [...colNames.keys()].map((colIdx) => `$${base + colIdx + 1}`);
109
- return `(${slots.join(", ")})`;
110
- });
111
- const params = rows.flatMap((row) => meta.columns.map((col) => encodeValue(col, row[col.name])));
112
- const columnList = colNames.map(quoteIdent).join(", ");
113
- const query = `insert into ${quoteIdent(meta.name)} (${columnList}) values ${groups.join(", ")} ${conflictClause(meta)}`;
114
- await sql.unsafe(query, params);
115
- };
116
- var restoreTable = async (sql, meta, rows) => {
117
- if (meta === undefined || rows.length === 0)
118
- return;
119
- await chunkRows(rows, RESTORE_CHUNK_ROWS).reduce(async (prev, part) => {
120
- await prev;
121
- return insertChunk(sql, meta, part);
122
- }, Promise.resolve());
123
- };
124
- var truncateAll = async (sql, names) => {
125
- if (names.length === 0)
126
- return;
127
- const list = names.map(quoteIdent).join(", ");
128
- await sql.unsafe(`truncate ${list} restart identity cascade`);
129
- };
130
- var runBackup = async (options) => {
131
- const sql = new SQL(options.url);
132
- const chosen = (await listTables(sql)).filter((name) => keepTable(name, options));
133
- const dumps = await Promise.all(chosen.map(async (name) => {
134
- const rows = await sql.unsafe(`select * from ${quoteIdent(name)}`);
135
- return [name, rows];
136
- }));
137
- await sql.end();
138
- const tables = Object.fromEntries(dumps);
139
- const payload = {
140
- at: new Date().toISOString(),
141
- tables,
142
- v: BACKUP_FORMAT_VERSION
143
- };
144
- const dir = options.out ?? join(process.cwd(), "backups");
145
- mkdirSync(dir, { recursive: true });
146
- const json = JSON.stringify(payload, (_, value) => typeof value === "bigint" ? value.toString() : value);
147
- const file = join(dir, `backup-${payload.at.replace(/[:.]/g, "-")}.json`);
148
- writeFileSync(file, json);
149
- writeFileSync(join(dir, "latest.json"), json);
150
- const total = chosen.reduce((sum, name) => sum + (tables[name]?.length ?? 0), 0);
151
- console.log(paint(`\u2713 backup \u2192 ${file}`, colors.green));
152
- console.log(paint(` ${chosen.length} tables, ${total} rows`, colors.dim));
153
- };
154
- var runRestore = async (file, options) => {
155
- if (!existsSync(file))
156
- throw new Error(`Backup not found: ${file}`);
157
- const payload = JSON.parse(readFileSync(file, "utf-8"));
158
- const names = Object.keys(payload.tables).filter((name) => keepTable(name, options));
159
- const sql = new SQL(options.url);
160
- const order = dependencyOrder(names, await foreignLinks(sql));
161
- const metas = await Promise.all(order.map((name) => tableMeta(sql, name)));
162
- const metaByName = new Map(metas.map((meta) => [meta.name, meta]));
163
- const aborted = options.truncate && !options.yes && prompt(paint(`\u26A0 TRUNCATE ${order.length} tables before restore? type "yes": `, colors.yellow)) !== "yes";
164
- if (aborted) {
165
- await sql.end();
166
- console.log(paint("aborted", colors.yellow));
167
- return;
168
- }
169
- if (options.truncate)
170
- await truncateAll(sql, [...order].reverse());
171
- await order.reduce(async (prev, name) => {
172
- await prev;
173
- return restoreTable(sql, metaByName.get(name), payload.tables[name] ?? []);
174
- }, Promise.resolve());
175
- await sql.end();
176
- const total = order.reduce((sum, name) => sum + (payload.tables[name]?.length ?? 0), 0);
177
- console.log(paint(`\u2713 restored ${order.length} tables, ${total} rows (idempotent upsert by primary key)`, colors.green));
178
- };
179
- var runSeed = async (entry) => {
180
- const target = entry ?? SEED_CANDIDATES.find((candidate) => existsSync(join(process.cwd(), candidate)));
181
- if (target === undefined)
182
- throw new Error(`No seed script found (looked for ${SEED_CANDIDATES.join(", ")}). Pass a path: absolute db seed <file>.`);
183
- console.log(paint(`seeding via ${target}\u2026`, colors.cyan));
184
- const proc = spawn(["bun", "run", target], {
185
- stderr: "inherit",
186
- stdin: "inherit",
187
- stdout: "inherit"
188
- });
189
- const code = await proc.exited;
190
- if (code !== 0)
191
- throw new Error(`Seed failed (exit ${code}).`);
192
- };
193
- var flagValue = (rest, flag) => {
194
- const idx = rest.indexOf(flag);
195
- return idx === UNFOUND_INDEX ? undefined : rest[idx + 1];
196
- };
197
- var listValue = (rest, flag) => (flagValue(rest, flag) ?? "").split(",").map((part) => part.trim()).filter((part) => part !== "");
198
- var parseOptions = (rest) => ({
199
- exclude: listValue(rest, "--exclude"),
200
- only: listValue(rest, "--only"),
201
- out: flagValue(rest, "--out"),
202
- truncate: rest.includes("--truncate"),
203
- url: resolveUrl(flagValue(rest, "--url")),
204
- yes: rest.includes("--yes") || rest.includes("-y")
205
- });
206
- var positionalArgs = (rest) => rest.filter((arg, idx) => !arg.startsWith("-") && !VALUE_FLAGS.includes(rest[idx - 1] ?? ""));
207
- var usage = () => {
208
- console.error("Usage: absolute db <backup|restore|seed> [options]");
209
- console.error(" backup [--out <dir>] [--only a,b] [--exclude a,b] [--url <url>] Dump tables \u2192 JSON (+ latest.json)");
210
- console.error(" restore [file] [--truncate] [--only a,b] [--exclude a,b] [-y] Idempotent upsert by primary key");
211
- console.error(" seed [file] Run the project\u2019s seed script");
212
- process.exit(1);
213
- };
214
- var runDb = async (args) => {
215
- const [sub, ...rest] = args;
216
- if (sub === "backup") {
217
- await runBackup(parseOptions(rest));
218
- return;
219
- }
220
- if (sub === "restore") {
221
- const file = positionalArgs(rest)[0] ?? join(process.cwd(), "backups", "latest.json");
222
- await runRestore(file, parseOptions(rest));
223
- return;
224
- }
225
- if (sub === "seed") {
226
- await runSeed(positionalArgs(rest)[0]);
227
- return;
228
- }
229
- usage();
230
- };
231
- export {
232
- chunkRows,
233
- conflictClause,
234
- dependencyOrder,
235
- encodeValue,
236
- quoteIdent,
237
- runDb
238
- };