@lunora/cli 1.0.0-alpha.467 → 1.0.0-alpha.469
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packem_chunks/cli.mjs +2 -2
- package/dist/packem_chunks/handler.mjs +43 -29
- package/dist/packem_chunks/handler27.mjs +14 -14
- package/dist/packem_shared/js-literal-FWLi94Lh.mjs +1 -0
- package/package.json +10 -10
- package/skills/lunora-setup-hyperdrive/SKILL.md +17 -3
- package/skills/lunora-setup-hyperdrive-global/SKILL.md +24 -11
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{existsSync as O,readFileSync as k}from"node:fs";import{writeFile as I,mkdir as U}from"node:fs/promises";import{parseSchema as B,applyAdditiveEdit as
|
|
1
|
+
import{existsSync as O,readFileSync as k}from"node:fs";import{writeFile as I,mkdir as U}from"node:fs/promises";import{parseSchema as B,applyAdditiveEdit as Y,projectUsesUmbrella as q}from"@lunora/config";import{join as E}from"@visulima/path";import{d as F}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as T}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{createRequire as P}from"node:module";import{LunoraError as S}from"@lunora/errors";import{addArgsFor as H,detectPackageManager as K}from"@lunora/config/cli";import{l as f}from"../packem_shared/js-literal-FWLi94Lh.mjs";const j=/^\//,G=(e,r)=>{try{const{args:n,command:t}=H(K(e),[r],{dev:!0});return`${t} ${n.join(" ")}`}catch{return`<your-package-manager> add -D ${r}`}},L=(e,r,n)=>{const t=P(E(n,"noop.cjs"));try{t.resolve(e)}catch{throw new S("INTERNAL",`\`lunora introspect\` needs the \`${r}\` driver to read this database, and it isn't installed.
|
|
2
2
|
|
|
3
3
|
Install it in your project:
|
|
4
4
|
|
|
5
|
-
${G(n,r)}`)}return t(e)},Q=e=>{const r=e.slice(0,Math.max(0,e.indexOf(":"))).toLowerCase();if(r==="postgres"||r==="postgresql")return"postgres";if(r==="mysql"||r==="mariadb")return"mysql";throw new
|
|
5
|
+
${G(n,r)}`)}return t(e)},Q=e=>{const r=e.slice(0,Math.max(0,e.indexOf(":"))).toLowerCase();if(r==="postgres"||r==="postgresql")return"postgres";if(r==="mysql"||r==="mariadb")return"mysql";throw new S("INTERNAL",`Unrecognised database URL scheme \`${r}\`. Expected a \`postgres://\`, \`postgresql://\`, \`mysql://\`, or \`mariadb://\` connection string.`)},W=e=>{try{const r=new URL(e).pathname.replace(j,"");return r===""?void 0:decodeURIComponent(r)}catch{return}},J=async(e,r,n,t=process.cwd())=>{if(r==="postgres"){const m=L("pg","pg",t).Client,u=new m({connectionString:e});return await u.connect(),{close:async()=>{await u.end()},execute:async(l,p)=>(await u.query(l,[...p])).rows,schema:n??"public"}}const a=L("mysql2/promise","mysql2",t).createConnection,o=await a(e),i=n??W(e);if(i===void 0)throw new S("INTERNAL","A MySQL connection string must name a database (`mysql://host/<database>`), or pass `--schema`.");return{close:async()=>{await o.end()},execute:async(c,m)=>{const[u]=await o.execute(c,[...m]);return Array.isArray(u)?u:[]},schema:i}},h=new Set(["_creationTime","_id"]),X={bigint:"v.bigint()",bigserial:"v.bigint()",bit:"v.string()",bool:"v.boolean()",boolean:"v.boolean()",box:"v.string()",bpchar:"v.string()",bytea:"v.bytes()",char:"v.string()",character:"v.string()","character varying":"v.string()",cidr:"v.string()",circle:"v.string()",citext:"v.string()",date:"v.date()","double precision":"v.number()",float4:"v.number()",float8:"v.number()",inet:"v.string()",int:"v.number()",int2:"v.number()",int4:"v.number()",int8:"v.bigint()",integer:"v.number()",interval:"v.string()",json:"v.any()",jsonb:"v.any()",line:"v.string()",lseg:"v.string()",macaddr:"v.string()",money:"v.number()",name:"v.string()",numeric:"v.number()",path:"v.string()",point:"v.string()",polygon:"v.string()",real:"v.number()",serial:"v.number()",smallint:"v.number()",smallserial:"v.number()",text:"v.string()",time:"v.string()","time with time zone":"v.string()","time without time zone":"v.string()",timestamp:"v.timestamp()","timestamp with time zone":"v.timestamp()","timestamp without time zone":"v.timestamp()",timestamptz:"v.timestamp()",tsquery:"v.string()",tsvector:"v.string()",uuid:"v.string()",varbit:"v.string()",varchar:"v.string()",xml:"v.string()"},z={bigint:"v.bigint()",binary:"v.bytes()",bit:"v.number()",blob:"v.bytes()",char:"v.string()",date:"v.date()",datetime:"v.timestamp()",decimal:"v.number()",double:"v.number()",enum:"v.string()",float:"v.number()",int:"v.number()",integer:"v.number()",json:"v.any()",longblob:"v.bytes()",longtext:"v.string()",mediumblob:"v.bytes()",mediumint:"v.number()",mediumtext:"v.string()",numeric:"v.number()",set:"v.string()",smallint:"v.number()",text:"v.string()",time:"v.string()",timestamp:"v.timestamp()",tinyblob:"v.bytes()",tinyint:"v.number()",tinytext:"v.string()",varbinary:"v.bytes()",varchar:"v.string()",year:"v.number()"},w=(e,r)=>{const t=(r==="postgres"?X:z)[e.dataType],s=e.references===void 0?t:`v.id(${JSON.stringify(e.references.table)})`;let a=s??"v.any()";for(let o=0;o<e.arrayDepth;o+=1)a=`v.array(${a})`;return e.nullable&&(a=`v.optional(${a})`),{expression:a,known:s!==void 0}},M=/^[A-Z_$][\w$]*$/i,Z=/[^\dA-Z]+(.)?/gi,V=/^\d+/,ee=/[^\w-]/g,N=e=>M.test(e)?e:f(e),ne=e=>M.test(e)?`.${e}`:`[${f(e)}]`,x=e=>e.replaceAll("*/",String.raw`*\/`),te=e=>{const r=e.replaceAll(ee,"_");return r===""?"table":r},re=e=>{const n=e.replaceAll(Z,(t,s)=>s?.toUpperCase()??"").replace(V,"");return n===""?"table":`${n.charAt(0).toLowerCase()}${n.slice(1)}`},D=e=>e.columns.filter(r=>!h.has(r.name)),ae=e=>{const r=new Set(e.primaryKey);for(const n of e.indexes)for(const t of n.columns)r.add(t);for(const n of e.columns)n.references!==void 0&&r.add(n.name);return[...r].filter(n=>!h.has(n))},C=(e,r,n,t)=>e.references===void 0||r.has(e.references.table)?e:(t.push(`${n}.${e.name}: references \`${e.references.table}\`, which isn't in the generated schema — emitted as a plain column instead of \`v.id(...)\`.`),{arrayDepth:e.arrayDepth,dataType:e.dataType,name:e.name,nullable:e.nullable}),se=(e,r,n,t)=>{const s=[];for(const o of e.columns){if(h.has(o.name)){t.push(`${e.name}.${o.name}: skipped — \`${o.name}\` is a Lunora system column. Rename it in the source database or map it by hand.`);continue}const i=C(o,n,e.name,t),{expression:c,known:m}=w(i,r);!m&&i.references===void 0&&(t.push(`${e.name}.${o.name}: no mapping for SQL type \`${o.dataType}\` — emitted as \`v.any()\`.`),s.push(` // TODO: \`${x(o.dataType)}\` has no direct validator; narrow this.`)),s.push(` ${N(o.name)}: ${c},`)}const a=[' .global({ backend: "hyperdrive" })'];if(e.primaryKey.length>0&&e.primaryKey.every(o=>!h.has(o))){const o=e.primaryKey.map(i=>f(i)).join(", ");a.push(` .index(${f(`by_${e.primaryKey.join("_")}`)}, [${o}], { unique: true })`)}for(const o of e.indexes){const i=o.columns.filter(u=>!h.has(u));if(i.length===0)continue;const c=i.map(u=>f(u)).join(", "),m=o.unique?", { unique: true }":"";a.push(` .index(${f(o.name)}, [${c}]${m})`)}return` ${N(e.name)}: defineTable({
|
|
6
6
|
${s.join(`
|
|
7
7
|
`)}
|
|
8
8
|
})
|
|
9
9
|
${a.join(`
|
|
10
|
-
`)},`},
|
|
10
|
+
`)},`},oe=(e,r,n)=>{const t=new Set(e.tables.map(a=>a.name)),s=e.tables.map(a=>se(a,e.dialect,t,n)).join(`
|
|
11
11
|
`);return`/**
|
|
12
12
|
* Generated by \`lunora introspect\` from an existing ${e.dialect==="postgres"?"Postgres":"MySQL"} database.
|
|
13
13
|
*
|
|
@@ -22,7 +22,7 @@ import { defineSchema, defineTable, v } from "${r.serverImport}";
|
|
|
22
22
|
export default defineSchema({
|
|
23
23
|
${s}
|
|
24
24
|
});
|
|
25
|
-
`},
|
|
25
|
+
`},ie=(e,r,n,t=new Set([e.name]),s=[])=>{const a=re(e.name),o=ae(e),i=new Map(D(e).map(l=>[l.name,l])),c=o.map(l=>{const p=i.get(l);if(p===void 0)return;const v=C({...p,nullable:!1},t,e.name,[]),{expression:d,known:A}=w(v,r);return!A&&v.references===void 0&&s.push(`${e.name}.${l}: filter falls back to \`v.any()\` (no mapping for SQL type \`${p.dataType}\`).`),` ${N(l)}: ${d},`}).filter(l=>l!==void 0),m=[f("_creationTime"),...o.map(l=>f(l))].join(", "),u=`ctx.db${ne(e.name)}`;return`/**
|
|
26
26
|
* Generated by \`lunora introspect\` for the \`${x(e.name)}\` table — a
|
|
27
27
|
* starting point you own and edit.
|
|
28
28
|
*
|
|
@@ -50,26 +50,26 @@ ${c.join(`
|
|
|
50
50
|
export const list = c.query.input(${a}List.args).query(({ args, ctx }) => ${u}.findMany(${a}List.toQueryArgs(args)));
|
|
51
51
|
|
|
52
52
|
export const get = c.query.input({ id: v.id(${f(e.name)}) }).query(({ args, ctx }) => ${u}.get(args.id));
|
|
53
|
-
`},
|
|
53
|
+
`},ce=(e,r)=>{const n=[],t=new Set(e.tables.map(a=>a.name)),s=[{contents:oe(e,r,n),path:"schema.ts"}];if(r.procedures){const a=new Map([["schema","the generated schema module"]]);for(const o of e.tables){if(D(o).length===0){n.push(`${o.name}: no usable columns — procedure module skipped.`);continue}const i=te(o.name);i!==o.name&&n.push(`${o.name}: written as \`${i}.ts\` — the table name isn't usable as a filename.`);const c=a.get(i);if(c!==void 0){n.push(`${o.name}: procedure module skipped — its filename \`${i}.ts\` collides with table \`${c}\`.`);continue}a.set(i,o.name),s.push({contents:ie(o,e.dialect,r,t,n),path:`${i}.ts`})}}return{files:s,warnings:n}},y=/^[A-Z_$][\w$]*$/i,me=e=>e.startsWith("v.optional(")&&e.endsWith(")")?e.slice(11,-1):e,le=(e,r,n)=>{const t=[],s=new Set(r?.columns.map(a=>a.name));for(const a of e.columns){if(h.has(a.name)||s.has(a.name))continue;if(!y.test(a.name)){n.warnings.push(`${e.name}.${a.name}: skipped — column names must be bare identifiers to merge.`);continue}const o=a.references!==void 0&&!n.present.has(a.references.table),{expression:i}=w(o?{...a,references:void 0}:a,n.dialect);t.push({column:a.name,kind:"addOptionalColumn",table:e.name,validator:me(i)}),!a.nullable&&r!==void 0&&n.warnings.push(`${e.name}.${a.name}: added as \`v.optional(...)\` — a required column on an existing table needs a backfill migration.`)}return t},ue=(e,r,n)=>{const t=[],s=new Set(r?.indexes.map(a=>a.name));for(const a of e.indexes){const o=a.columns.filter(i=>!h.has(i));if(!(s.has(a.name)||o.length===0)){if(!y.test(a.name)||!o.every(i=>y.test(i))){n.push(`${e.name}: index \`${a.name}\` skipped — index and column names must be bare identifiers to merge.`);continue}t.push({fields:o,kind:"addIndex",name:a.name,table:e.name,...a.unique?{unique:!0}:{}})}}return t},de=(e,r,n)=>{const t=[],s=[],a=new Map(r.map(i=>[i.name,i])),o=new Set([...a.keys(),...e.tables.map(i=>i.name)]);for(const i of e.tables){if(!y.test(i.name)){s.push(`${i.name}: skipped — merging into an existing schema needs a table name that is a bare identifier.`);continue}const c=a.get(i.name);c===void 0&&t.push({global:{backend:"hyperdrive"},kind:"addTable",table:i.name}),t.push(...le(i,c,{dialect:n,present:o,warnings:s}),...ue(i,c,s))}return{edits:t,warnings:s}},pe=(e,r,n)=>{const t=B(e);if(!t.ok)return{applied:0,warnings:[`lunora/schema.ts could not be parsed (${t.reason}) — leave it alone and merge by hand, or pass --force.`]};const s=de(r,t.tables,n),a=[...s.warnings];let o=e,i=0;for(const c of s.edits){const m=Y(o,c);m.ok?(o=m.text,i+=1):a.push(`${c.table}: skipped one edit (${m.reason}).`)}return{applied:i,warnings:a,...i===0?{}:{text:o}}},g=(e,r)=>{const n=e[r]??e[r.toLowerCase()]??e[r.toUpperCase()];return typeof n=="string"?n:typeof n=="number"||typeof n=="bigint"||typeof n=="boolean"?String(n):""},ge=`
|
|
54
54
|
SELECT c.table_name, c.column_name, c.is_nullable, c.data_type, c.udt_name
|
|
55
55
|
FROM information_schema.columns c
|
|
56
56
|
JOIN information_schema.tables t
|
|
57
57
|
ON t.table_schema = c.table_schema AND t.table_name = c.table_name
|
|
58
58
|
WHERE c.table_schema = $1 AND t.table_type = 'BASE TABLE'
|
|
59
|
-
ORDER BY c.table_name, c.ordinal_position`,
|
|
59
|
+
ORDER BY c.table_name, c.ordinal_position`,fe=`
|
|
60
60
|
SELECT tc.table_name, kcu.column_name
|
|
61
61
|
FROM information_schema.table_constraints tc
|
|
62
62
|
JOIN information_schema.key_column_usage kcu
|
|
63
63
|
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
|
|
64
64
|
WHERE tc.table_schema = $1 AND tc.constraint_type = 'PRIMARY KEY'
|
|
65
|
-
ORDER BY tc.table_name, kcu.ordinal_position`,
|
|
65
|
+
ORDER BY tc.table_name, kcu.ordinal_position`,ve=`
|
|
66
66
|
SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table, ccu.column_name AS foreign_column
|
|
67
67
|
FROM information_schema.table_constraints tc
|
|
68
68
|
JOIN information_schema.key_column_usage kcu
|
|
69
69
|
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
|
|
70
70
|
JOIN information_schema.constraint_column_usage ccu
|
|
71
71
|
ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema
|
|
72
|
-
WHERE tc.table_schema = $1 AND tc.constraint_type = 'FOREIGN KEY'`,
|
|
72
|
+
WHERE tc.table_schema = $1 AND tc.constraint_type = 'FOREIGN KEY'`,be=`
|
|
73
73
|
SELECT t.relname AS table_name, i.relname AS index_name, ix.indisunique AS is_unique, a.attname AS column_name
|
|
74
74
|
FROM pg_class t
|
|
75
75
|
JOIN pg_index ix ON t.oid = ix.indrelid
|
|
@@ -78,22 +78,22 @@ export const get = c.query.input({ id: v.id(${f(e.name)}) }).query(({ args, ctx
|
|
|
78
78
|
CROSS JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord)
|
|
79
79
|
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
|
|
80
80
|
WHERE n.nspname = $1 AND t.relkind = 'r' AND NOT ix.indisprimary
|
|
81
|
-
ORDER BY t.relname, i.relname, k.ord`,
|
|
81
|
+
ORDER BY t.relname, i.relname, k.ord`,he=`
|
|
82
82
|
SELECT c.TABLE_NAME, c.COLUMN_NAME, c.IS_NULLABLE, c.DATA_TYPE
|
|
83
83
|
FROM information_schema.COLUMNS c
|
|
84
84
|
JOIN information_schema.TABLES t
|
|
85
85
|
ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
|
|
86
86
|
WHERE c.TABLE_SCHEMA = ? AND t.TABLE_TYPE = 'BASE TABLE'
|
|
87
|
-
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`,
|
|
87
|
+
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`,_e=`
|
|
88
88
|
SELECT TABLE_NAME, COLUMN_NAME
|
|
89
89
|
FROM information_schema.KEY_COLUMN_USAGE
|
|
90
90
|
WHERE TABLE_SCHEMA = ? AND CONSTRAINT_NAME = 'PRIMARY'
|
|
91
|
-
ORDER BY TABLE_NAME, ORDINAL_POSITION`,
|
|
91
|
+
ORDER BY TABLE_NAME, ORDINAL_POSITION`,Ee=`
|
|
92
92
|
SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME AS foreign_table, REFERENCED_COLUMN_NAME AS foreign_column
|
|
93
93
|
FROM information_schema.KEY_COLUMN_USAGE
|
|
94
|
-
WHERE TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME IS NOT NULL`,
|
|
94
|
+
WHERE TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME IS NOT NULL`,ye=`
|
|
95
95
|
SELECT TABLE_NAME, INDEX_NAME AS index_name, NON_UNIQUE, COLUMN_NAME
|
|
96
96
|
FROM information_schema.STATISTICS
|
|
97
97
|
WHERE TABLE_SCHEMA = ? AND INDEX_NAME <> 'PRIMARY'
|
|
98
|
-
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX`,
|
|
99
|
-
`).length)} lines)`),!1):(await U(r,{recursive:!0}),await I(t,e.contents,"utf8"),n.logger.info(`wrote lunora/${e.path}`),!0)}
|
|
98
|
+
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX`,_=e=>{const r=new Map;for(const n of e){const t=g(n,"table_name"),s=r.get(t);s===void 0?r.set(t,[n]):s.push(n)}return r},Ae=(e,r)=>{const n=g(e,"data_type").toLowerCase();if(r!=="postgres")return{arrayDepth:0,dataType:n};const t=g(e,"udt_name").toLowerCase();return n==="array"&&t.startsWith("_")?{arrayDepth:1,dataType:t.slice(1)}:{arrayDepth:0,dataType:t===""?n:t}},Ne=(e,r)=>{const n=new Map;for(const t of e){const s=g(t,"index_name"),a=r==="postgres"?g(t,"is_unique")==="true":g(t,"NON_UNIQUE")==="0",o=n.get(s);o===void 0?n.set(s,{columns:[g(t,"column_name")],unique:a}):o.columns.push(g(t,"column_name"))}return[...n.entries()].map(([t,s])=>({columns:s.columns,name:t,unique:s.unique}))},Se=async(e,r,n)=>{const t=r==="postgres",[s,a,o,i]=await Promise.all([e(t?ge:he,[n]),e(t?fe:_e,[n]),e(t?ve:Ee,[n]),e(t?be:ye,[n])]),c=_(a),m=_(o),u=_(i),l=[];for(const[p,v]of _(s)){const d=new Map((m.get(p)??[]).map(b=>[g(b,"column_name"),{column:g(b,"foreign_column"),table:g(b,"foreign_table")}])),A=v.map(b=>{const R=g(b,"column_name"),$=d.get(R);return{...Ae(b,r),name:R,nullable:g(b,"is_nullable").toUpperCase()==="YES",...$===void 0?{}:{references:$}}});l.push({columns:A,indexes:Ne(u.get(p)??[],r),name:p,primaryKey:(c.get(p)??[]).map(b=>g(b,"column_name"))})}return l.sort((p,v)=>p.name.localeCompare(v.name)),{dialect:r,tables:l}},we=e=>q(e)?"lunorash/server":"@lunora/server",Re=async(e,r,n)=>{const t=E(r,e.path);return O(t)&&n.force!==!0?(n.logger.warn(`skipped lunora/${e.path} — it already exists (pass --force to overwrite)`),!1):n.dryRun===!0?(n.logger.info(`would write lunora/${e.path} (${String(e.contents.split(`
|
|
99
|
+
`).length)} lines)`),!1):(await U(r,{recursive:!0}),await I(t,e.contents,"utf8"),n.logger.info(`wrote lunora/${e.path}`),!0)},$e=(e,r)=>{const n=r.tables;if(n===void 0||n.length===0)return e.tables;for(const t of n)e.tables.some(s=>s.name===t)||r.logger.warn(`--tables: no table named "${t}" exists in this schema — skipped.`);return e.tables.filter(t=>n.includes(t.name))},Te=async(e,r,n,t)=>{const s=pe(k(e,"utf8"),r,n);for(const a of s.warnings)t.logger.warn(a);return s.text===void 0?(t.logger.info("lunora/schema.ts is already up to date with the database."),!1):t.dryRun===!0?(t.logger.info(`would merge ${String(s.applied)} addition(s) into lunora/schema.ts`),!1):(await I(e,s.text,"utf8"),t.logger.info(`merged ${String(s.applied)} addition(s) into lunora/schema.ts`),!0)},Le=async e=>{const r=e.cwd??process.cwd();if(e.connection===void 0&&(e.url===void 0||e.url==="")){const d="`lunora introspect` needs a database URL: pass --url, or set DATABASE_URL.";return e.logger.error(d),{code:T.USAGE,error:d,written:[]}}const n=e.connection===void 0?Q(e.url):e.dialect??"postgres",t=e.connection??await J(e.url,n,e.schema,r);let s;try{s=await Se(t.execute,n,t.schema)}finally{e.connection===void 0&&await t.close()}const a=$e(s,e);if(a.length===0){const d="no tables found to introspect — check --schema and --tables.";return e.logger.error(d),{code:T.NOT_FOUND,error:d,written:[]}}const{files:o,warnings:i}=ce({...s,tables:a},{procedures:e.procedures!==!1,serverImport:we(r)}),c=E(r,"lunora"),m=[],u=E(c,"schema.ts"),l=O(u)&&e.force!==!0,p=l?o.filter(d=>d.path!=="schema.ts"):o;l&&await Te(u,{...s,tables:a},n,e)&&m.push("schema.ts");for(const d of p)await Re(d,c,e)&&m.push(d.path);for(const d of i)e.logger.warn(d);e.logger.info(`introspected ${String(a.length)} table(s) from ${n}. Review the generated files before running \`lunora dev\`.`);const v=a.map(d=>d.name);return{code:0,dialect:n,tables:v,written:m}},qe=F(async({cwd:e,format:r,logger:n,options:t})=>{const s=await Le({cwd:e,dryRun:t.dryRun===!0,force:t.force===!0,format:r,logger:n,procedures:t.procedures!==!1,schema:t.schema,tables:t.tables===void 0?void 0:t.tables.split(",").map(a=>a.trim()),url:t.url??process.env.DATABASE_URL});return{code:s.code,data:s.dialect===void 0?void 0:{dialect:s.dialect,dryRun:t.dryRun===!0,tables:s.tables??[],written:s.written},error:s.error}});export{qe as execute,we as resolveServerImport,Le as runIntrospectCommand};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=/[\u003C\u003E\u002F\u2028\u2029]/g,S={"/":String.raw`\u002F`,"\u2028":String.raw`\u2028`,"\u2029":String.raw`\u2029`,"<":String.raw`\u003C`,">":String.raw`\u003E`},n=u=>JSON.stringify(u).replaceAll(t,r=>S[r]??r);export{n as l};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/cli",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.469",
|
|
4
4
|
"description": "The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -56,16 +56,16 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@bomb.sh/tab": "0.0.22",
|
|
59
|
-
"@lunora/advisor": "1.0.0-alpha.
|
|
60
|
-
"@lunora/bindings": "1.0.0-alpha.
|
|
61
|
-
"@lunora/codegen": "1.0.0-alpha.
|
|
62
|
-
"@lunora/config": "1.0.0-alpha.
|
|
63
|
-
"@lunora/container": "1.0.0-alpha.
|
|
64
|
-
"@lunora/d1": "1.0.0-alpha.
|
|
59
|
+
"@lunora/advisor": "1.0.0-alpha.283",
|
|
60
|
+
"@lunora/bindings": "1.0.0-alpha.149",
|
|
61
|
+
"@lunora/codegen": "1.0.0-alpha.388",
|
|
62
|
+
"@lunora/config": "1.0.0-alpha.445",
|
|
63
|
+
"@lunora/container": "1.0.0-alpha.107",
|
|
64
|
+
"@lunora/d1": "1.0.0-alpha.230",
|
|
65
65
|
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
66
|
-
"@lunora/mcp": "1.0.0-alpha.
|
|
67
|
-
"@lunora/runtime": "1.0.0-alpha.
|
|
68
|
-
"@lunora/seed": "1.0.0-alpha.
|
|
66
|
+
"@lunora/mcp": "1.0.0-alpha.287",
|
|
67
|
+
"@lunora/runtime": "1.0.0-alpha.271",
|
|
68
|
+
"@lunora/seed": "1.0.0-alpha.278",
|
|
69
69
|
"@lunora/wire": "1.0.0-alpha.6",
|
|
70
70
|
"@visulima/cerebro": "4.0.1",
|
|
71
71
|
"@visulima/error": "6.1.0",
|
|
@@ -79,18 +79,28 @@ thunk instead. It runs once per shard construction, not per request:
|
|
|
79
79
|
```ts title="src/server.ts"
|
|
80
80
|
import type { HyperdriveLike } from "@lunora/hyperdrive";
|
|
81
81
|
import { createHyperdrive, fromPostgresJs } from "@lunora/hyperdrive";
|
|
82
|
-
import postgres from "postgres";
|
|
83
82
|
|
|
84
83
|
import { defineApp } from "../lunora/_generated/app.js";
|
|
85
84
|
|
|
86
85
|
const app = defineApp<Env>()
|
|
87
86
|
.shard((env) => env.SHARD)
|
|
88
|
-
.hyperdrive((env) =>
|
|
87
|
+
.hyperdrive((env) =>
|
|
88
|
+
fromPostgresJs(async () => {
|
|
89
|
+
const { default: postgres } = await import("postgres");
|
|
90
|
+
|
|
91
|
+
return postgres(createHyperdrive(env.HYPERDRIVE as HyperdriveLike).connectionString, { fetch_types: false, max: 5 });
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
89
94
|
.build();
|
|
90
95
|
|
|
91
96
|
export const ShardDO = app.ShardDO;
|
|
92
97
|
```
|
|
93
98
|
|
|
99
|
+
The adapters take a loader, not a driver. It runs on the first query, so the
|
|
100
|
+
driver stays out of the Worker's startup graph; never `import postgres from
|
|
101
|
+
"postgres"` statically in the entry. For `pg`, the loader may `await
|
|
102
|
+
client.connect()` before returning.
|
|
103
|
+
|
|
94
104
|
Then query from any action:
|
|
95
105
|
|
|
96
106
|
```ts
|
|
@@ -152,7 +162,11 @@ messages: defineTable({ body: v.string(), channelId: v.string() })
|
|
|
152
162
|
|
|
153
163
|
```ts
|
|
154
164
|
// worker entry: one client per binding name
|
|
155
|
-
.sourceClient((env, binding) => fromPostgresJs(
|
|
165
|
+
.sourceClient((env, binding) => fromPostgresJs(async () => {
|
|
166
|
+
const { default: postgres } = await import("postgres");
|
|
167
|
+
|
|
168
|
+
return postgres((env[binding] as { connectionString: string }).connectionString, { fetch_types: false, max: 5 });
|
|
169
|
+
}))
|
|
156
170
|
```
|
|
157
171
|
|
|
158
172
|
How `.source()` behaves:
|
|
@@ -36,7 +36,7 @@ same shapes D1 uses (boolean → 1/0, JSON → text/json, bigint → decimal).
|
|
|
36
36
|
```bash
|
|
37
37
|
pnpm add @lunora/hyperdrive @lunora/sql-store
|
|
38
38
|
pnpm add postgres # Postgres engine → buildPgExec(fromPostgresJs(...))
|
|
39
|
-
# or: pnpm add mysql2 → buildMysqlExec(mysql2/promise pool), MySQL 8.0+
|
|
39
|
+
# or: pnpm add mysql2 → buildMysqlExec(loader of a mysql2/promise pool), MySQL 8.0+
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
`@lunora/hyperdrive/global` is a subpath export of `@lunora/hyperdrive`, not a
|
|
@@ -96,7 +96,6 @@ Codegen emits a `.hyperdriveGlobal({ engine, exec, origin? })` builder method.
|
|
|
96
96
|
```ts
|
|
97
97
|
import { fromPostgresJs } from "@lunora/hyperdrive";
|
|
98
98
|
import { buildPgExec } from "@lunora/hyperdrive/global";
|
|
99
|
-
import postgres from "postgres";
|
|
100
99
|
|
|
101
100
|
import { defineApp } from "../lunora/_generated/app.js";
|
|
102
101
|
|
|
@@ -104,7 +103,14 @@ const app = defineApp<Env>()
|
|
|
104
103
|
.shard((env) => env.SHARD)
|
|
105
104
|
.hyperdriveGlobal({
|
|
106
105
|
engine: "postgres",
|
|
107
|
-
exec: (env) =>
|
|
106
|
+
exec: (env) =>
|
|
107
|
+
buildPgExec(
|
|
108
|
+
fromPostgresJs(async () => {
|
|
109
|
+
const { default: postgres } = await import("postgres");
|
|
110
|
+
|
|
111
|
+
return postgres(env.HYPERDRIVE.connectionString, { fetch_types: false, max: 5 });
|
|
112
|
+
}),
|
|
113
|
+
),
|
|
108
114
|
})
|
|
109
115
|
.build();
|
|
110
116
|
|
|
@@ -113,26 +119,33 @@ export const ShardDO = app.ShardDO;
|
|
|
113
119
|
|
|
114
120
|
For MySQL, create the pool with the `FOUND_ROWS` flag. The optimistic-concurrency
|
|
115
121
|
guard needs matched-row counts. Without the flag, an idempotent write reports
|
|
116
|
-
zero affected rows and raises a spurious conflict. `buildMysqlExec`
|
|
117
|
-
|
|
122
|
+
zero affected rows and raises a spurious conflict. `buildMysqlExec` takes a loader,
|
|
123
|
+
like `fromPostgresJs`, and runs it on the first statement; that statement fails when the
|
|
124
|
+
flag is missing. If it can't read the flag, it warns once instead. Import `mysql2` inside
|
|
125
|
+
the loader, never at the top of the file, so the driver stays out of the Worker's startup.
|
|
118
126
|
|
|
119
127
|
```ts
|
|
120
128
|
import { buildMysqlExec } from "@lunora/hyperdrive/global";
|
|
121
|
-
import mysql from "mysql2/promise";
|
|
122
129
|
|
|
123
130
|
// inside the builder chain:
|
|
124
131
|
.hyperdriveGlobal({
|
|
125
132
|
engine: "mysql",
|
|
126
133
|
// disableEval: mysql2 needs it to run on Workers.
|
|
127
|
-
exec: (env) =>
|
|
134
|
+
exec: (env) =>
|
|
135
|
+
buildMysqlExec(async () => {
|
|
136
|
+
const { createPool } = await import("mysql2/promise");
|
|
137
|
+
|
|
138
|
+
return createPool({ uri: env.HYPERDRIVE.connectionString, disableEval: true, flags: ["FOUND_ROWS"] });
|
|
139
|
+
}),
|
|
128
140
|
})
|
|
129
141
|
```
|
|
130
142
|
|
|
131
143
|
Each write commits its row and its aggregate, rank and CDC companion rows in one
|
|
132
|
-
transaction
|
|
133
|
-
(`
|
|
134
|
-
|
|
135
|
-
|
|
144
|
+
transaction. postgres.js (`fromPostgresJs`, through `sql.begin`) and a mysql2 **pool**
|
|
145
|
+
(`getConnection`) can hold that transaction. A postgres.js client without `begin` and a
|
|
146
|
+
single mysql2 connection cannot, so a write to a table with companions fails there with
|
|
147
|
+
`GLOBAL_TRANSACTION_UNSUPPORTED` before it writes anything. `fromNodePg` has no such
|
|
148
|
+
handle, so its companions commit one statement at a time after the row. Every query and write
|
|
136
149
|
statement, and each write's transaction as a whole, is abandoned after 30 seconds
|
|
137
150
|
with a retryable `SERVICE_UNAVAILABLE` (`createHyperdriveGlobalCtxDb` takes a
|
|
138
151
|
`queryTimeoutMs` to change it; `0` disables it). Table, aggregate, rank and CDC
|