@lunora/seed 1.0.0-alpha.101 → 1.0.0-alpha.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,7 @@
36
36
 
37
37
  `@lunora/seed` populates a Lunora database with realistic, production-like fake data derived from your `defineSchema`. It introspects every table, maps each field to a generator (field-name aware — a `string` column called `email` becomes an email address, `firstName` a first name, and so on), resolves foreign keys by inserting parent tables before their children, and lets you override any value.
38
38
 
39
- Generation is **deterministic**: it is built on a vendored, input-hashed generator (a rebuilt [`copycat`](https://github.com/supabase-community/copycat)) layered over [`@faker-js/faker`](https://fakerjs.dev). The same `seed` value and schema always produce the same rows, so fixtures are reproducible across runs and machines.
39
+ Generation is **deterministic**: it is built on a vendored, input-hashed generator (a rebuilt [`copycat`](https://github.com/supabase-community/copycat)) layered over [`@faker-js/faker`](https://fakerjs.dev). Generation is deterministic in `seed` alone for every column except the time-valued ones: a `number` column named like a timestamp (`createdAt`, `expiresAt`, …) is generated relative to the wall clock, so two runs with the same `seed` differ unless you also pin `now` (`--now` on the CLI). Ids are unaffected. Pin the `(seed, now)` pair and a plan is byte-identical across runs and machines.
40
40
 
41
41
  Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-safe, real-time backend on Cloudflare Workers + Durable Objects with a Vite-first DX.
42
42
 
@@ -121,7 +121,7 @@ Supply a `persist` hook (`SeedClientOptions.persist`) to write each batch as it
121
121
  ```bash
122
122
  lunora seed --count 25 # 25 rows per table (default 10)
123
123
  lunora seed --table posts --count 100 # one table (FK parents seeded automatically)
124
- lunora seed --seed 42 # reproducible run
124
+ lunora seed --seed 42 --now 1750000000000 # byte-identical run (pin both)
125
125
  lunora seed --reset # wipe local .wrangler/state first (local dev only)
126
126
  lunora seed --dry-run # print NDJSON, write nothing
127
127
  ```
@@ -130,7 +130,7 @@ Also: `--batch-size` (rows per HTTP request, default 500), `--url` (worker URL,
130
130
 
131
131
  ## Limitations
132
132
 
133
- - **`.unique()` columns are not enforced.** Each value is hashed independently, so a unique column over a small value space (a bounded integer, a boolean, a short enum) can collide across rows. Strings such as emails and uuids are effectively unique in practice. Colliding rows are rejected by the import path rather than silently overwritten.
133
+ - **`.unique()` columns are dealt distinct values by construction.** Each value is a function of the row's absolute index, not a hash of the row, so two rows in the same run never collide — including across the several calls `indexOffset` exists to support. A column whose domain is too small to cover the requested count (a boolean, a three-literal `v.union()`) is refused at plan time, with the column named, before anything is inserted.
134
134
  - **Seeding is deterministic by design.** Re-running with the same `--seed` regenerates identical `_id`s, which the import path skips as conflicts. Use a different `--seed` for fresh rows, or `--reset` to wipe local state first.
135
135
 
136
136
  > This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs/packages/seed)**.
package/dist/index.d.mts CHANGED
@@ -44,6 +44,14 @@ type SeedClient<InsertModel> = SeedClientState & { [Table in keyof InsertModel]:
44
44
  interface SeedClientOptions {
45
45
  /** Default row count when a table call passes no spec (default `10`). */
46
46
  defaultCount?: number;
47
+ /**
48
+ * Wall-clock reference for time-valued columns (`createdAt`, `expiresAt`, a
49
+ * `.unique()` date, …), in epoch-ms. Defaults to `Date.now()` **per call**,
50
+ * which is the one input that otherwise drifts between runs — pin it and the
51
+ * client is deterministic in the full sense the docs promise, not merely in
52
+ * its ids.
53
+ */
54
+ now?: number;
47
55
  /** Persist each generated batch (e.g. insert through the test harness). Pure when omitted. */
48
56
  persist?: (table: string, rows: ReadonlyArray<Record<string, unknown>>) => Promise<void> | void;
49
57
  /** Deterministic mapping selector — same seed ⇒ same rows and ids. Default `0`. */
package/dist/index.d.ts CHANGED
@@ -44,6 +44,14 @@ type SeedClient<InsertModel> = SeedClientState & { [Table in keyof InsertModel]:
44
44
  interface SeedClientOptions {
45
45
  /** Default row count when a table call passes no spec (default `10`). */
46
46
  defaultCount?: number;
47
+ /**
48
+ * Wall-clock reference for time-valued columns (`createdAt`, `expiresAt`, a
49
+ * `.unique()` date, …), in epoch-ms. Defaults to `Date.now()` **per call**,
50
+ * which is the one input that otherwise drifts between runs — pin it and the
51
+ * client is deterministic in the full sense the docs promise, not merely in
52
+ * its ids.
53
+ */
54
+ now?: number;
47
55
  /** Persist each generated batch (e.g. insert through the test harness). Pure when omitted. */
48
56
  persist?: (table: string, rows: ReadonlyArray<Record<string, unknown>>) => Promise<void> | void;
49
57
  /** Deterministic mapping selector — same seed ⇒ same rows and ids. Default `0`. */
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createSeedClient as o}from"./packem_shared/createSeedClient-DCeTN5tl.mjs";import{s as a}from"./packem_shared/plan-vnUMmTjD.mjs";export{o as createSeedClient,a as seedPlan};
1
+ import{createSeedClient as o}from"./packem_shared/createSeedClient-DODhDlgB.mjs";import{s as a}from"./packem_shared/plan-CZy5Xjiw.mjs";export{o as createSeedClient,a as seedPlan};
@@ -0,0 +1 @@
1
+ import{a as $,s as C,c as P}from"./plan-CZy5Xjiw.mjs";const T=e=>Array.isArray(e)&&e.length===2&&typeof e[0]=="number"&&typeof e[1]=="number",A=(e,u,l,i)=>{const o=n=>typeof n=="number"?n:P.int([l,u,"count"],{max:n[1],min:n[0]});return e===void 0?{count:i}:typeof e=="number"?{count:e}:typeof e=="function"?{count:e(o)}:T(e)?{count:o(e)}:{count:e.length,partials:e}},q=(e,u,l)=>{const i={};if(e!==void 0){const o=new Set(e.flatMap(n=>Object.keys(n)));for(const n of o)i[n]=f=>e[f.index-l]?.[n]}for(const[o,n]of Object.entries(u??{}))i[o]=n;return i},H=(e,u={})=>{const{defaultCount:l=10,now:i,persist:o,seed:n=0}=u,f={},c={},a={},w=async(t,s,y)=>{$(n);const{count:d,partials:b}=A(s,t,n,l),m=a[t]??0,g=C(e,{counts:{[t]:d},existingIds:c,indexOffset:{[t]:m},now:i,only:[t],overrides:{[t]:q(b,y?.overrides,m)},seed:n});let k=[];for(const{rows:v,table:r}of g){const j=v.map(S=>S._id);f[r]??=[],f[r].push(...v),c[r]??=[],c[r].push(...j),a[r]=(a[r]??0)+v.length,r===t&&(k=j),o!==void 0&&await o(r,v)}return{[t]:k}};let h=Promise.resolve();const x=(t,s,y)=>{const d=h.then(()=>w(t,s,y));return h=d.then(()=>{},()=>{}),d},O={$ids:c,$reset:()=>{for(const t of Object.keys(f))delete f[t];for(const t of Object.keys(c))delete c[t];for(const t of Object.keys(a))delete a[t]},$store:f};return new Proxy(O,{get(t,s,y){return typeof s=="string"&&!s.startsWith("$")&&Object.hasOwn(e.tables,s)?(d,b)=>x(s,d,b):Reflect.get(t,s,y)}})};export{H as createSeedClient};
@@ -0,0 +1 @@
1
+ import{LunoraError as S}from"@lunora/errors";import{faker as f}from"@faker-js/faker";import{optionalInner as Z}from"@lunora/values";let $=0;const le=(e,t)=>e<t?-1:e>t?1:0,R=e=>e===void 0?"undefined":typeof e=="bigint"?`${e.toString()}n`:e===null||typeof e!="object"?JSON.stringify(e)??"null":Array.isArray(e)?`[${e.map(n=>R(n)).join(",")}]`:`{${Object.keys(e).toSorted(le).map(n=>`${JSON.stringify(n)}:${R(e[n])}`).join(",")}}`,X=(e,t)=>{let n=3735928559^t,r=1103547991^t;for(let o=0;o<e.length;o+=1){const s=e.codePointAt(o)??0;n=Math.imul(n^s,2654435761),r=Math.imul(r^s,1597334677)}return n=Math.imul(n^n>>>16,2246822507),n^=Math.imul(r^r>>>13,3266489909),r=Math.imul(r^r>>>16,2246822507),r^=Math.imul(n^n>>>13,3266489909),4294967296*(2097151&r)+(n>>>0)},me="\0",de=e=>{const t=typeof e=="string"?me+e:R(e);return X(t,$)%4294967296},fe=e=>{if(typeof e=="number"){$=e>>>0;return}if(typeof e=="string"){$=X(e,0)%4294967296;return}let t=0;for(const n of e)t=Math.imul(t^n,2654435761)>>>0;$=t},h=(e,t)=>(f.seed(de(e)),t()),he=(e,t)=>{if(typeof t=="number")return t;const[n,r]=t;return h(["__count__",e],()=>f.number.int({max:r,min:n}))},ge=/[a-z]/,pe=/[A-Z]/,ve=/\d/,u={bool(e){return h(e,()=>f.datatype.boolean())},city(e){return h(e,()=>f.location.city())},country(e){return h(e,()=>f.location.country())},email(e){return h(e,()=>f.internet.email().toLowerCase())},firstName(e){return h(e,()=>f.person.firstName())},float(e,t){const{fractionDigits:n=2,max:r=1e3,min:o=0}=t??{};return h(e,()=>f.number.float({fractionDigits:n,max:r,min:o}))},fullName(e){return h(e,()=>f.person.fullName())},int(e,t){const{max:n=1e3,min:r=0}=t??{};return h(e,()=>f.number.int({max:n,min:r}))},lastName(e){return h(e,()=>f.person.lastName())},oneOf(e,t){if(t.length!==0)return h(e,()=>f.helpers.arrayElement(t))},paragraph(e){return h(e,()=>f.lorem.paragraph())},password(e){return h(e,()=>f.internet.password())},phoneNumber(e){return h(e,()=>f.phone.number())},scramble(e,t){const n=new Set(t?.preserve);return Array.from({length:e.length},(r,o)=>{const s=e.charAt(o);return n.has(s)?s:ge.test(s)?h([e,o,"l"],()=>f.string.alpha({casing:"lower",length:1})):pe.test(s)?h([e,o,"u"],()=>f.string.alpha({casing:"upper",length:1})):ve.test(s)?h([e,o,"d"],()=>String(f.number.int({max:9,min:0}))):s}).join("")},sentence(e,t){return h(e,()=>f.lorem.sentence(t?{max:t.max??8,min:t.min??3}:void 0))},slug(e){return h(e,()=>f.lorem.slug())},streetAddress(e){return h(e,()=>f.location.streetAddress())},times(e,t,n){const r=he(e,t),o=[];for(let s=0;s<r;s+=1)o.push(n([e,s]));return o},url(e){return h(e,()=>f.internet.url())},username(e){return h(e,()=>f.internet.username())},uuid(e){return h(e,()=>f.string.uuid())},word(e){return h(e,()=>f.lorem.word())}},p=e=>e._meta??{},ee=e=>Z(e)??e,te=e=>{const{column:t}=p(e);if(t?.defaultValue!==void 0||t?.defaultFn!==void 0)return!0;const n=Z(e);return n===void 0?!1:te(n)},we=(e,t)=>{const n=ee(t),r=p(n);return{fkTable:n.kind==="id"?r.tableName:void 0,hasServerDefault:te(t),kind:n.kind,name:e,nullable:r.column?.notNull===!1,optional:t.kind==="optional",unique:r.column?.unique===!0,validator:n}},be=e=>{const{tables:t}=e;return Object.entries(t).map(([n,r])=>({fields:Object.entries(r.shape).map(([o,s])=>we(o,s)),name:n}))},ye=(e,t)=>{const n=new Map(e.map(a=>[a.name,a])),r=a=>{const c=n.get(a);if(c===void 0)return new Set;const l=new Set;for(const d of c.fields)d.fkTable!==void 0&&d.fkTable!==a&&t.has(d.fkTable)&&l.add(d.fkTable);return l},o=[],s=new Set,i=[...t].filter(a=>n.has(a));for(;i.length>0;){const a=i.findIndex(d=>[...r(d)].every(g=>s.has(g))),c=a===-1?0:a,[l]=i.splice(c,1);if(l===void 0)break;o.push(l),s.add(l)}return o},Se=(e,t,n=new Set)=>{const r=new Map(e.map(i=>[i.name,i])),o=new Set(t),s=[...o];for(;s.length>0;){const i=s.pop();if(i===void 0)break;const a=r.get(i);if(a!==void 0&&!n.has(i))for(const c of a.fields)c.fkTable!==void 0&&c.fkTable!==i&&r.has(c.fkTable)&&!o.has(c.fkTable)&&(o.add(c.fkTable),s.push(c.fkTable))}return o},ne=e=>p(e).constraints??{},Ae=[{generate:e=>u.email(e),keywords:["email"]},{generate:e=>u.firstName(e),keywords:["firstname"]},{generate:e=>u.lastName(e),keywords:["lastname","surname"]},{generate:e=>u.username(e),keywords:["username"]},{generate:e=>u.fullName(e),keywords:["name"]},{generate:e=>u.sentence(e,{max:5,min:2}),keywords:["title"]},{generate:e=>u.url(e),keywords:["url","link","image","avatar"]},{generate:e=>u.phoneNumber(e),keywords:["phone"]},{generate:e=>u.paragraph(e),keywords:["description","bio","body","content","text"]},{generate:e=>u.slug(e),keywords:["slug","key","code"]},{generate:e=>u.city(e),keywords:["city"]},{generate:e=>u.country(e),keywords:["country"]},{generate:e=>u.streetAddress(e),keywords:["address","street"]},{generate:e=>u.password(e),keywords:["password","secret","token"]}],xe=new Set(["at","date","deadline","expires","expiry","since","timestamp","until"]),Te=/([a-z\d])([A-Z])/gu,Ie=/[\s_-]+/u,ke=e=>e.replaceAll(Te,"$1 $2").split(Ie).filter(t=>t!=="").map(t=>t.toLowerCase()),P={max:1e3,min:0},q={max:1e6,min:0},Oe=4320*60*60*1e3,re=e=>{const n=ke(e).at(-1);return n!==void 0&&xe.has(n)},H=(e,t)=>t-u.int(e,{max:Oe,min:0}),N=(e,t)=>{throw new S("INTERNAL",`@lunora/seed: column "${e}" ${t}. Supply a value via \`overrides\` (\`{ <table>: { ${e}: … } }\`) or restrict the run with \`only\` so the table is skipped.`)},j=(e,t)=>{const n=e===""?"x":e,r=Math.min(Math.max(n.length,t.min),t.max);return n.padEnd(r,n).slice(0,r)},T="@example.com",_="https://",$e=/[\s@]/gu,Ne=/[^a-z\d]/gu,_e=(e,t)=>{const n=e.lastIndexOf("@"),r=n>0?e.slice(n):"",o=r.length>0&&r.length<t.max?r:T,s=(n>0?e.slice(0,n):e).replaceAll($e,"");return`${j(s,{max:t.max-o.length,min:Math.max(1,t.min-o.length)})}${o}`},Me=(e,t)=>{const n=URL.canParse(e)?new URL(e).hostname.replaceAll(Ne,""):"";return`${_}${j(n,{max:t.max-_.length,min:Math.max(1,t.min-_.length)})}`},Ee={email:{fit:_e,generate:e=>u.email(e),minimum:T.length+1},uri:{fit:Me,generate:e=>u.url(e),minimum:_.length+1}},De=(e,t)=>{const n=e.toLowerCase(),r=Ae.find(o=>o.keywords.some(s=>n.includes(s)));return r===void 0?u.word(t):r.generate(t)},Le=(e,t,n)=>{const{format:r,maxLength:o,minLength:s,pattern:i}=n;if(i!==void 0)return N(e,`is constrained to the pattern /${i}/, and the seeder cannot invent a value matching an arbitrary regular expression`);if(o!==void 0&&s!==void 0&&s>o)throw new S("INTERNAL",`Seed constraint error for field "${e}": minLength (${String(s)}) > maxLength (${String(o)}). Adjust the schema constraints.`);const a={max:o??Number.POSITIVE_INFINITY,min:s??0};if(r===void 0)return j(De(e,t),a);const c=Ee[r];if(c===void 0)return N(e,`declares format "${r}", which the seeder has no generator for`);if(a.max<c.minimum)return N(e,`declares format "${r}" with maxLength ${String(o)}, and the shortest ${r} value the seeder can build is ${String(c.minimum)} characters`);const l=c.generate(t);return l.length>=a.min&&l.length<=a.max?l:c.fit(l,a)},b=(e,t,n,r)=>{const o=ee(e),s=ne(o);if(s.enum!==void 0&&s.enum.length>0)return u.oneOf(n,s.enum);switch(o.kind){case"any":return u.word(n);case"array":{const i=p(o).inner;return i===void 0?[]:u.times(n,[1,3],a=>b(i,t,a,r))}case"bigint":return u.int(n,{max:s.maximum??q.max,min:s.minimum??q.min});case"boolean":return u.bool(n);case"bytes":return Array.from({length:8},(i,a)=>u.int([n,a],{max:255,min:0}));case"date":case"timestamp":return H(n,r);case"from":return N(t,"is a v.from() validator, whose external Standard Schema the seeder cannot introspect to invent a conforming value (give the column a concrete v.* type instead)");case"id":return u.uuid(n);case"literal":return p(o).value;case"null":return null;case"number":{const{maximum:i,minimum:a}=s;if(i===void 0&&a===void 0&&re(t))return H(n,r);if(i!==void 0&&a!==void 0&&a>i)throw new S("INTERNAL",`Seed constraint error for field "${t}": minimum (${String(a)}) > maximum (${String(i)}). Adjust the schema constraints.`);const c=a??P.min,l=i??P.max;return!Number.isInteger(c)||!Number.isInteger(l)?u.float(n,{max:l,min:c}):u.int(n,{max:l,min:c})}case"object":{const i=p(o).shape??{};return Object.fromEntries(Object.entries(i).map(([a,c])=>[a,b(c,a,[n,a],r)]))}case"record":{const{keyValidator:i,valueValidator:a}=p(o),c=u.times(n,[1,3],l=>{const d=i===void 0?u.word(["k",l]):String(b(i,t,["k",l],r)),g=a===void 0?u.word(["v",l]):b(a,t,["v",l],r);return[d,g]});return Object.fromEntries(c)}case"storage":return`seed/${u.uuid(n)}`;case"string":return Le(t,n,s);case"union":{const i=p(o).members??[],a=u.oneOf(n,i);return a===void 0?u.word(n):b(a,t,[n,"u"],r)}default:return u.word(n)}},L=1e6,Re=6e4,Pe=2**32,qe=(e,t,n)=>{const r=[...e];for(let o=r.length-1;o>0;o-=1){const s=u.int([t,n,"unique-deal",o],{max:o,min:0}),i=r[o];r[o]=r[s],r[s]=i}return r},je=(e,t,n)=>{const{maxLength:r}=n,o=String(t),s=e.indexOf("@");if(s>0){const c=e.slice(0,s),l=e.slice(s),d=r===void 0?c.length:r-l.length-o.length-1;if(d>=0)return`${c.slice(0,d)}+${o}${l}`}if(n.format==="email"){const c=r===void 0?e.length:Math.max(0,r-T.length-o.length);return`${e.slice(0,c)}${o}${T}`}const i=`-${o}`,a=r===void 0?e.length:Math.max(0,r-i.length);return`${e.slice(0,a)}${i}`},M=(e,t,n)=>{const r=qe(e,t,n);return{capacity:r.length,valueAt:o=>r[o%r.length]}},Ce=(e,t)=>{if(Number.isInteger(e)&&Number.isInteger(t)){const n=t-e+1;return{capacity:n,valueAt:r=>e+r%n}}return{capacity:L,valueAt:n=>e+n%L*(t-e)/L}},W=e=>({capacity:Number.POSITIVE_INFINITY,valueAt:t=>e-t*Re}),Fe={capacity:Number.POSITIVE_INFINITY,valueAt:e=>e},z={capacity:Number.POSITIVE_INFINITY,valueAt:(e,t)=>t()},J=(e,t)=>{const{maximum:n,minimum:r}=e;return n===void 0&&r===void 0?Fe:Ce(r??t.min,n??t.max)},K=(e,t,n)=>{throw new S("INTERNAL",`@lunora/seed: cannot generate unique values for "${e}"."${t}" — ${n}. Supply them via \`overrides\` (\`{ ${e}: { ${t}: … } }\`) or drop \`.unique()\` from the column.`)},Ue=({format:e,maxLength:t})=>{if(t===void 0)return Number.POSITIVE_INFINITY;const n=e==="email"?T.length:1;return t<=n?0:10**(t-n)},Ve=(e,t,n)=>(e.pattern!==void 0&&K(t,n,"the column is pattern-constrained, and an index-tagged value would no longer match it"),e.format!==void 0&&e.format!=="email"&&K(t,n,`the column declares format "${e.format}", which an index-tagged value would no longer satisfy`),{capacity:Ue(e),valueAt:(r,o)=>je(String(o()),r,e)}),Be=(e,t,n)=>M(e,t,n),Ye=(e,t)=>{const{now:n,table:r}=t,o=ne(e.validator),s=o.enum;if(Array.isArray(s)&&s.length>0)return M(s,r,e.name);switch(e.kind){case"any":case"string":return Ve(o,r,e.name);case"bigint":return J(o,q);case"boolean":return M([!1,!0],r,e.name);case"bytes":return{capacity:Pe,valueAt:i=>[Math.floor(i/16777216)%256,Math.floor(i/65536)%256,Math.floor(i/256)%256,i%256,...Array.from({length:4},(a,c)=>u.int([r,e.name,"unique-bytes",c],{max:255,min:0}))]};case"date":case"timestamp":return W(n);case"literal":return{capacity:1,valueAt:()=>p(e.validator).value};case"number":return o.maximum===void 0&&o.minimum===void 0&&re(e.name)?W(n):J(o,P);case"union":{const i=p(e.validator).members??[],a=i.filter(c=>c.kind==="literal").map(c=>p(c).value);return a.length>0&&a.length===i.length?M(a,r,e.name):z}default:return z}},Ge=(e,t)=>typeof e=="function"?e(t):e,He=(e,t)=>{if(!e.optional)return e.nullable?null:u.uuid(t)},oe=(e,t,n,r,o)=>{const s=e.fkTable,i=s===t?(r.get(t)??[]).slice(0,n):r.get(s)??[],a=o[s]??[];return a.length===0?i:[...i,...a]},Q=(e,t,n,r)=>[e,t,n,r],We=(e,t)=>{const{existingIds:n,idsByTable:r,index:o,input:s,localIndex:i,now:a,table:c,uniqueDeals:l}=t;if(e.fkTable!==void 0){const g=oe(e,c,i,r,n);if(g.length===0)return He(e,s);if(!e.unique)return u.oneOf(s,g);const I=l.get(e.name);return I===void 0?g.at(-1):I.valueAt(o,()=>u.oneOf(s,g))}if(e.hasServerDefault)return;const d=e.unique?l.get(e.name):void 0;return d!==void 0?d.valueAt(o,()=>b(e.validator,e.name,s,a)):b(e.validator,e.name,s,a)},ze=(e,t,n,r,o,s,i)=>{const a=new Map;for(const c of e){if(!c.unique||c.hasServerDefault&&c.fkTable===void 0||c.fkTable===t)continue;const l=c.fkTable===void 0?void 0:i(c);if(l?.length===0)continue;const d=l===void 0?Ye(c,{now:o,table:t}):Be(l,t,c.name),g=r+n;if(g>d.capacity&&!Object.hasOwn(s,c.name))throw new S("BAD_REQUEST",`cannot seed ${String(g)} rows into "${t}": unique column "${c.name}" has only ${String(d.capacity)} possible values`);a.set(c.name,d)}return a},Ze=(e,t={})=>{const{counts:n={},defaultCount:r=10,existingIds:o={},indexOffset:s={},now:i=Date.now(),only:a,overrides:c={},seed:l=0}=t;fe(l);const d=be(e);if(a!==void 0){const m=new Set(d.map(w=>w.name));for(const w of a)if(!m.has(w)){const A=d.map(k=>k.name).join(", ");throw new S("BAD_REQUEST",`unknown table "${w}" in seed \`only\` — schema defines: ${A||"(no tables)"}`)}}const g=new Set(a??d.map(m=>m.name)),I=new Set(Object.keys(o).filter(m=>!g.has(m)&&(o[m]??[]).length>0)),se=new Set([...Se(d,g,I)].filter(m=>g.has(m)||(o[m]??[]).length===0)),ae=ye(d,se),ie=new Map(d.map(m=>[m.name,m])),E=new Map,C={},F=[];for(const m of ae){const w=ie.get(m);if(w===void 0)continue;const A=c[m]??{},k=n[m]??r,U=s[m]??0,ce=ze(w.fields,m,k,U,i,A,y=>oe(y,m,0,E,o)),V=[];E.set(m,V);const D=[];C[m]=D;for(let y=0;y<k;y+=1){const O=U+y,x={},B=(v,ue)=>{if(Object.hasOwn(A,v)){const G=Ge(A[v],{field:v,index:O,row:x,store:C,table:m});if(G!==void 0){x[v]=G;return}}const Y=ue();Y!==void 0&&(x[v]=Y)};B("_id",()=>u.uuid(Q(l,m,O,"_id"))),V.push(x._id);for(const v of w.fields)B(v.name,()=>We(v,{existingIds:o,idsByTable:E,index:O,input:Q(l,m,O,v.name),localIndex:y,now:i,table:m,uniqueDeals:ce}));D.push(x)}F.push({rows:D,table:m})}return F};export{fe as a,u as c,be as i,Ze as s};
@@ -0,0 +1 @@
1
+ import{s as a}from"./plan-CZy5Xjiw.mjs";export{a as seedPlan};
package/dist/testing.mjs CHANGED
@@ -1 +1 @@
1
- import{s as m,i as u}from"./packem_shared/plan-vnUMmTjD.mjs";const g=(r,o,a)=>{if(o.size===0&&a.size===0)return r;const s={...r};for(const n of o){const t=s[n];typeof t=="number"&&(s[n]=BigInt(t))}for(const n of a){const t=s[n];Array.isArray(t)&&(s[n]=Uint8Array.from(t).buffer)}return s},S=async(r,o,a={})=>{const s=m(o,a),n=u(o),t=new Map,l=new Map;for(const i of n){const c=new Set(i.fields.filter(e=>e.kind==="bigint").map(e=>e.name)),f=new Set(i.fields.filter(e=>e.kind==="bytes").map(e=>e.name));c.size>0&&t.set(i.name,c),f.size>0&&l.set(i.name,f)}const d={};return await r.run(async i=>{const c=i.db.insert;for(const{rows:f,table:e}of s){const b=[],p=t.get(e)??new Set,w=l.get(e)??new Set;for(const y of f)b.push(await c(e,g(y,p,w),{allowExplicitId:!0}));d[e]=b}}),d};export{S as seed};
1
+ import{s as m,i as u}from"./packem_shared/plan-CZy5Xjiw.mjs";const g=(r,o,a)=>{if(o.size===0&&a.size===0)return r;const s={...r};for(const n of o){const t=s[n];typeof t=="number"&&(s[n]=BigInt(t))}for(const n of a){const t=s[n];Array.isArray(t)&&(s[n]=Uint8Array.from(t).buffer)}return s},S=async(r,o,a={})=>{const s=m(o,a),n=u(o),t=new Map,l=new Map;for(const i of n){const c=new Set(i.fields.filter(e=>e.kind==="bigint").map(e=>e.name)),f=new Set(i.fields.filter(e=>e.kind==="bytes").map(e=>e.name));c.size>0&&t.set(i.name,c),f.size>0&&l.set(i.name,f)}const d={};return await r.run(async i=>{const c=i.db.insert;for(const{rows:f,table:e}of s){const b=[],p=t.get(e)??new Set,w=l.get(e)??new Set;for(const y of f)b.push(await c(e,g(y,p,w),{allowExplicitId:!0}));d[e]=b}}),d};export{S as seed};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/seed",
3
- "version": "1.0.0-alpha.101",
3
+ "version": "1.0.0-alpha.103",
4
4
  "description": "Schema-driven, deterministic database seeding for Lunora: realistic fake data from defineSchema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@faker-js/faker": "^10.5.0",
51
- "@lunora/errors": "1.0.0-alpha.29"
51
+ "@lunora/errors": "1.0.0-alpha.30"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@lunora/server": ">=1.0.0-alpha.24 <2.0.0-0",
@@ -1 +0,0 @@
1
- import{a as S,s as $,c as C}from"./plan-vnUMmTjD.mjs";const P=e=>Array.isArray(e)&&e.length===2&&typeof e[0]=="number"&&typeof e[1]=="number",T=(e,d,u,r)=>{const o=n=>typeof n=="number"?n:C.int([u,d,"count"],{max:n[1],min:n[0]});return e===void 0?{count:r}:typeof e=="number"?{count:e}:typeof e=="function"?{count:e(o)}:P(e)?{count:o(e)}:{count:e.length,partials:e}},A=(e,d,u)=>{const r={};if(e!==void 0){const o=new Set(e.flatMap(n=>Object.keys(n)));for(const n of o)r[n]=i=>e[i.index-u]?.[n]}for(const[o,n]of Object.entries(d??{}))r[o]=n;return r},B=(e,d={})=>{const{defaultCount:u=10,persist:r,seed:o=0}=d,n={},i={},l={},j=async(t,s,a)=>{S(o);const{count:c,partials:v}=T(s,t,o,u),h=l[t]??0,g=$(e,{counts:{[t]:c},existingIds:i,indexOffset:{[t]:h},only:[t],overrides:{[t]:A(v,a?.overrides,h)},seed:o});let m=[];for(const{rows:y,table:f}of g){const k=y.map(w=>w._id);n[f]??=[],n[f].push(...y),i[f]??=[],i[f].push(...k),l[f]=(l[f]??0)+y.length,f===t&&(m=k),r!==void 0&&await r(f,y)}return{[t]:m}};let b=Promise.resolve();const x=(t,s,a)=>{const c=b.then(()=>j(t,s,a));return b=c.then(()=>{},()=>{}),c},O={$ids:i,$reset:()=>{for(const t of Object.keys(n))delete n[t];for(const t of Object.keys(i))delete i[t];for(const t of Object.keys(l))delete l[t]},$store:n};return new Proxy(O,{get(t,s,a){return typeof s=="string"&&!s.startsWith("$")&&Object.hasOwn(e.tables,s)?(c,v)=>x(s,c,v):Reflect.get(t,s,a)}})};export{B as createSeedClient};
@@ -1 +0,0 @@
1
- import{LunoraError as y}from"@lunora/errors";import{faker as m}from"@faker-js/faker";import{optionalInner as W}from"@lunora/values";let x=0;const oe=(e,t)=>e<t?-1:e>t?1:0,_=e=>e===void 0?"undefined":typeof e=="bigint"?`${e.toString()}n`:e===null||typeof e!="object"?JSON.stringify(e)??"null":Array.isArray(e)?`[${e.map(n=>_(n)).join(",")}]`:`{${Object.keys(e).toSorted(oe).map(n=>`${JSON.stringify(n)}:${_(e[n])}`).join(",")}}`,H=(e,t)=>{let n=3735928559^t,r=1103547991^t;for(let o=0;o<e.length;o+=1){const i=e.codePointAt(o)??0;n=Math.imul(n^i,2654435761),r=Math.imul(r^i,1597334677)}return n=Math.imul(n^n>>>16,2246822507),n^=Math.imul(r^r>>>13,3266489909),r=Math.imul(r^r>>>16,2246822507),r^=Math.imul(n^n>>>13,3266489909),4294967296*(2097151&r)+(n>>>0)},se="\0",ae=e=>{const t=typeof e=="string"?se+e:_(e);return H(t,x)%4294967296},ie=e=>{if(typeof e=="number"){x=e>>>0;return}if(typeof e=="string"){x=H(e,0)%4294967296;return}let t=0;for(const n of e)t=Math.imul(t^n,2654435761)>>>0;x=t},f=(e,t)=>(m.seed(ae(e)),t()),ce=(e,t)=>{if(typeof t=="number")return t;const[n,r]=t;return f(["__count__",e],()=>m.number.int({max:r,min:n}))},ue=/[a-z]/,le=/[A-Z]/,me=/\d/,u={bool(e){return f(e,()=>m.datatype.boolean())},city(e){return f(e,()=>m.location.city())},country(e){return f(e,()=>m.location.country())},dateString(e,t){const n=t?.min??new Date("1980-01-01T00:00:00.000Z"),r=t?.max??new Date("2020-01-01T00:00:00.000Z");return f(e,()=>m.date.between({from:n,to:r}).toISOString())},email(e){return f(e,()=>m.internet.email().toLowerCase())},firstName(e){return f(e,()=>m.person.firstName())},float(e,t){const{fractionDigits:n=2,max:r=1e3,min:o=0}=t??{};return f(e,()=>m.number.float({fractionDigits:n,max:r,min:o}))},fullName(e){return f(e,()=>m.person.fullName())},int(e,t){const{max:n=1e3,min:r=0}=t??{};return f(e,()=>m.number.int({max:n,min:r}))},lastName(e){return f(e,()=>m.person.lastName())},oneOf(e,t){if(t.length!==0)return f(e,()=>m.helpers.arrayElement(t))},paragraph(e){return f(e,()=>m.lorem.paragraph())},password(e){return f(e,()=>m.internet.password())},phoneNumber(e){return f(e,()=>m.phone.number())},scramble(e,t){const n=new Set(t?.preserve);return Array.from({length:e.length},(r,o)=>{const i=e.charAt(o);return n.has(i)?i:ue.test(i)?f([e,o,"l"],()=>m.string.alpha({casing:"lower",length:1})):le.test(i)?f([e,o,"u"],()=>m.string.alpha({casing:"upper",length:1})):me.test(i)?f([e,o,"d"],()=>String(m.number.int({max:9,min:0}))):i}).join("")},sentence(e,t){return f(e,()=>m.lorem.sentence(t?{max:t.max??8,min:t.min??3}:void 0))},slug(e){return f(e,()=>m.lorem.slug())},streetAddress(e){return f(e,()=>m.location.streetAddress())},times(e,t,n){const r=ce(e,t),o=[];for(let i=0;i<r;i+=1)o.push(n([e,i]));return o},url(e){return f(e,()=>m.internet.url())},username(e){return f(e,()=>m.internet.username())},uuid(e){return f(e,()=>m.string.uuid())},word(e){return f(e,()=>m.lorem.word())}},p=e=>e._meta??{},Z=e=>W(e)??e,z=e=>{const{column:t}=p(e);if(t?.defaultValue!==void 0||t?.defaultFn!==void 0)return!0;const n=W(e);return n===void 0?!1:z(n)},de=(e,t)=>{const n=Z(t),r=p(n);return{fkTable:n.kind==="id"?r.tableName:void 0,hasServerDefault:z(t),kind:n.kind,name:e,nullable:r.column?.notNull===!1,optional:t.kind==="optional",unique:r.column?.unique===!0,validator:n}},fe=e=>{const{tables:t}=e;return Object.entries(t).map(([n,r])=>({fields:Object.entries(r.shape).map(([o,i])=>de(o,i)),name:n}))},he=(e,t)=>{const n=new Map(e.map(s=>[s.name,s])),r=s=>{const c=n.get(s);if(c===void 0)return new Set;const l=new Set;for(const h of c.fields)h.fkTable!==void 0&&h.fkTable!==s&&t.has(h.fkTable)&&l.add(h.fkTable);return l},o=[],i=new Set,a=[...t].filter(s=>n.has(s));for(;a.length>0;){const s=a.findIndex(h=>[...r(h)].every(g=>i.has(g))),c=s===-1?0:s,[l]=a.splice(c,1);if(l===void 0)break;o.push(l),i.add(l)}return o},ge=(e,t,n=new Set)=>{const r=new Map(e.map(a=>[a.name,a])),o=new Set(t),i=[...o];for(;i.length>0;){const a=i.pop();if(a===void 0)break;const s=r.get(a);if(s!==void 0&&!n.has(a))for(const c of s.fields)c.fkTable!==void 0&&c.fkTable!==a&&r.has(c.fkTable)&&!o.has(c.fkTable)&&(o.add(c.fkTable),i.push(c.fkTable))}return o},J=e=>p(e).constraints??{},ve=[{generate:e=>u.email(e),keywords:["email"]},{generate:e=>u.firstName(e),keywords:["firstname"]},{generate:e=>u.lastName(e),keywords:["lastname","surname"]},{generate:e=>u.username(e),keywords:["username"]},{generate:e=>u.fullName(e),keywords:["name"]},{generate:e=>u.sentence(e,{max:5,min:2}),keywords:["title"]},{generate:e=>u.url(e),keywords:["url","link","image","avatar"]},{generate:e=>u.phoneNumber(e),keywords:["phone"]},{generate:e=>u.paragraph(e),keywords:["description","bio","body","content","text"]},{generate:e=>u.slug(e),keywords:["slug","key","code"]},{generate:e=>u.city(e),keywords:["city"]},{generate:e=>u.country(e),keywords:["country"]},{generate:e=>u.streetAddress(e),keywords:["address","street"]},{generate:e=>u.password(e),keywords:["password","secret","token"]}],pe=new Set(["at","date","deadline","expires","expiry","since","timestamp","until"]),we=/([a-z\d])([A-Z])/gu,be=/[\s_-]+/u,ye=e=>e.replaceAll(we,"$1 $2").split(be).filter(t=>t!=="").map(t=>t.toLowerCase()),$={max:1e3,min:0},E={max:1e6,min:0},Se=4320*60*60*1e3,K=e=>{const n=ye(e).at(-1);return n!==void 0&&pe.has(n)},Ae=(e,t)=>t-u.int(e,{max:Se,min:0}),Te=(e,t,n)=>{const r=e.toLowerCase(),o=ve.find(l=>l.keywords.some(h=>r.includes(h))),i=o===void 0?u.word(t):o.generate(t),{maxLength:a,minLength:s}=n;if(a!==void 0&&s!==void 0&&s>a)throw new y("INTERNAL",`Seed constraint error for field "${e}": minLength (${String(s)}) > maxLength (${String(a)}). Adjust the schema constraints.`);const c=a!==void 0&&i.length>a?i.slice(0,a):i;return s!==void 0&&c.length<s?c.padEnd(s,c.length>0?c:"x"):c},b=(e,t,n,r)=>{const o=Z(e),i=J(o);if(i.enum!==void 0&&i.enum.length>0)return u.oneOf(n,i.enum);switch(o.kind){case"any":return u.word(n);case"array":{const a=p(o).inner;return a===void 0?[]:u.times(n,[1,3],s=>b(a,t,s,r))}case"bigint":return u.int(n,{max:E.max,min:E.min});case"boolean":return u.bool(n);case"bytes":return Array.from({length:8},(a,s)=>u.int([n,s],{max:255,min:0}));case"date":case"timestamp":return new Date(u.dateString(n)).getTime();case"from":throw new y("INTERNAL",`@lunora/seed: column "${t}" is a v.from() validator, whose external Standard Schema the seeder cannot introspect to invent a conforming value. Supply one via \`overrides\` (\`{ <table>: { ${t}: … } }\`), restrict the run with \`only\` so the table is skipped, or give the column a concrete v.* type.`);case"id":return u.uuid(n);case"literal":return p(o).value;case"null":return null;case"number":{const{maximum:a,minimum:s}=i;if(a===void 0&&s===void 0&&K(t))return Ae(n,r);if(a!==void 0&&s!==void 0&&s>a)throw new y("INTERNAL",`Seed constraint error for field "${t}": minimum (${String(s)}) > maximum (${String(a)}). Adjust the schema constraints.`);const c=s??$.min,l=a??$.max;return!Number.isInteger(c)||!Number.isInteger(l)?u.float(n,{max:l,min:c}):u.int(n,{max:l,min:c})}case"object":{const a=p(o).shape??{};return Object.fromEntries(Object.entries(a).map(([s,c])=>[s,b(c,s,[n,s],r)]))}case"record":{const{keyValidator:a,valueValidator:s}=p(o),c=u.times(n,[1,3],l=>{const h=a===void 0?u.word(["k",l]):String(b(a,t,["k",l],r)),g=s===void 0?u.word(["v",l]):b(s,t,["v",l],r);return[h,g]});return Object.fromEntries(c)}case"storage":return`seed/${u.uuid(n)}`;case"string":return Te(t,n,i);case"union":{const a=p(o).members??[],s=u.oneOf(n,a);return s===void 0?u.word(n):b(s,t,[n,"u"],r)}default:return u.word(n)}},O=1e6,Ie=6e4,ke=2**32,xe=(e,t,n)=>{const r=[...e];for(let o=r.length-1;o>0;o-=1){const i=u.int([t,n,"unique-deal",o],{max:o,min:0}),a=r[o];r[o]=r[i],r[i]=a}return r},F="@example.com",Ne=(e,t,n)=>{const{maxLength:r}=n,o=String(t),i=e.indexOf("@");if(i>0){const c=e.slice(0,i),l=e.slice(i),h=r===void 0?c.length:Math.max(0,r-l.length-o.length-1);return`${c.slice(0,h)}+${o}${l}`}if(n.format==="email"){const c=r===void 0?e.length:Math.max(0,r-F.length-o.length);return`${e.slice(0,c)}${o}${F}`}const a=`-${o}`,s=r===void 0?e.length:Math.max(0,r-a.length);return`${e.slice(0,s)}${a}`},B=(e,t,n)=>{const r=xe(e,t,n);return{capacity:r.length,valueAt:o=>r[o%r.length]}},Oe=(e,t)=>{if(Number.isInteger(e)&&Number.isInteger(t)){const n=t-e+1;return{capacity:n,valueAt:r=>e+r%n}}return{capacity:O,valueAt:n=>e+n%O*(t-e)/O}},V=e=>({capacity:Number.POSITIVE_INFINITY,valueAt:t=>e-t*Ie}),_e={capacity:Number.POSITIVE_INFINITY,valueAt:e=>e},U=(e,t)=>{const{maximum:n,minimum:r}=e;return n===void 0&&r===void 0?_e:Oe(r??t.min,n??t.max)},G=(e,t,n)=>{throw new y("INTERNAL",`@lunora/seed: cannot generate unique values for "${e}"."${t}" — ${n}. Supply them via \`overrides\` (\`{ ${e}: { ${t}: … } }\`) or drop \`.unique()\` from the column.`)},$e=e=>e===void 0?Number.POSITIVE_INFINITY:e<=1?0:10**(e-1),Ee=(e,t,n)=>(e.pattern!==void 0&&G(t,n,"the column is pattern-constrained, and an index-tagged value would no longer match it"),e.format!==void 0&&e.format!=="email"&&G(t,n,`the column declares format "${e.format}", which an index-tagged value would no longer satisfy`),{capacity:$e(e.maxLength),valueAt:(r,o)=>Ne(String(o()),r,e)}),Me=(e,t)=>{const{now:n,table:r}=t,o=J(e.validator),i=o.enum;if(Array.isArray(i)&&i.length>0)return B(i,r,e.name);switch(e.kind){case"any":case"string":return Ee(o,r,e.name);case"bigint":return U(o,E);case"boolean":return B([!1,!0],r,e.name);case"bytes":return{capacity:ke,valueAt:a=>[Math.floor(a/16777216)%256,Math.floor(a/65536)%256,Math.floor(a/256)%256,a%256,...Array.from({length:4},(s,c)=>u.int([r,e.name,"unique-bytes",c],{max:255,min:0}))]};case"date":case"timestamp":return V(n);case"literal":return{capacity:1,valueAt:()=>p(e.validator).value};case"number":return o.maximum===void 0&&o.minimum===void 0&&K(e.name)?V(n):U(o,$);default:return{capacity:Number.POSITIVE_INFINITY,valueAt:(a,s)=>s()}}},De=(e,t)=>typeof e=="function"?e(t):e,Le=(e,t)=>{if(!e.optional)return e.nullable?null:u.uuid(t)},Re=(e,t,n,r,o)=>{const i=e.fkTable,a=i===t?(r.get(t)??[]).slice(0,n):r.get(i)??[],s=o[i]??[];return s.length===0?a:[...a,...s]},Y=(e,t,n,r)=>[e,t,n,r],Pe=(e,t)=>{const{existingIds:n,idsByTable:r,index:o,input:i,localIndex:a,now:s,table:c,uniqueDeals:l}=t;if(e.fkTable!==void 0){const g=Re(e,c,a,r,n);return g.length===0?Le(e,i):u.oneOf(i,g)}if(e.hasServerDefault)return;const h=e.unique?l.get(e.name):void 0;return h!==void 0?h.valueAt(o,()=>b(e.validator,e.name,i,s)):b(e.validator,e.name,i,s)},je=(e,t,n,r,o,i)=>{const a=new Map;for(const s of e){if(!s.unique||s.fkTable!==void 0||s.hasServerDefault)continue;const c=Me(s,{now:o,table:t}),l=r+n;if(l>c.capacity&&!Object.hasOwn(i,s.name))throw new y("BAD_REQUEST",`cannot seed ${String(l)} rows into "${t}": unique column "${s.name}" has only ${String(c.capacity)} possible values`);a.set(s.name,c)}return a},Be=(e,t={})=>{const{counts:n={},defaultCount:r=10,existingIds:o={},indexOffset:i={},now:a=Date.now(),only:s,overrides:c={},seed:l=0}=t;ie(l);const h=fe(e);if(s!==void 0){const d=new Set(h.map(w=>w.name));for(const w of s)if(!d.has(w)){const S=h.map(T=>T.name).join(", ");throw new y("BAD_REQUEST",`unknown table "${w}" in seed \`only\` — schema defines: ${S||"(no tables)"}`)}}const g=new Set(s??h.map(d=>d.name)),Q=new Set(Object.keys(o).filter(d=>!g.has(d)&&(o[d]??[]).length>0)),X=new Set([...ge(h,g,Q)].filter(d=>g.has(d)||(o[d]??[]).length===0)),ee=he(h,X),te=new Map(h.map(d=>[d.name,d])),M=new Map,D={},L=[];for(const d of ee){const w=te.get(d);if(w===void 0)continue;const S=c[d]??{},T=n[d]??r,R=i[d]??0,ne=je(w.fields,d,T,R,a,S),P=[];M.set(d,P);const N=[];D[d]=N;for(let I=0;I<T;I+=1){const k=R+I,A={},j=(v,re)=>{if(Object.hasOwn(S,v)){const C=De(S[v],{field:v,index:k,row:A,store:D,table:d});if(C!==void 0){A[v]=C;return}}const q=re();q!==void 0&&(A[v]=q)};j("_id",()=>u.uuid(Y(l,d,k,"_id"))),P.push(A._id);for(const v of w.fields)j(v.name,()=>Pe(v,{existingIds:o,idsByTable:M,index:k,input:Y(l,d,k,v.name),localIndex:I,now:a,table:d,uniqueDeals:ne}));N.push(A)}L.push({rows:N,table:d})}return L};export{ie as a,u as c,fe as i,Be as s};
@@ -1 +0,0 @@
1
- import{s as a}from"./plan-vnUMmTjD.mjs";export{a as seedPlan};