@lunora/seed 1.0.0-alpha.13 → 1.0.0-alpha.130

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/LICENSE.md CHANGED
@@ -103,3 +103,9 @@ Unless required by applicable law or agreed to in writing, software distributed
103
103
  under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
104
  CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
105
  specific language governing permissions and limitations under the License.
106
+
107
+ <!-- DEPENDENCIES -->
108
+ <!-- /DEPENDENCIES -->
109
+
110
+ <!-- TYPE_DEPENDENCIES -->
111
+ <!-- /TYPE_DEPENDENCIES -->
package/README.md CHANGED
@@ -36,7 +36,9 @@
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
+ Generated addresses always sit on the RFC 2606 reserved domain `example.com`, which accepts no mail a seeded row can never turn into real email to a real stranger when a welcome job, a digest or an auth verification runs over it. Override the column when you need a domain of your own.
40
+
41
+ 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
42
 
41
43
  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
44
 
@@ -121,7 +123,7 @@ Supply a `persist` hook (`SeedClientOptions.persist`) to write each batch as it
121
123
  ```bash
122
124
  lunora seed --count 25 # 25 rows per table (default 10)
123
125
  lunora seed --table posts --count 100 # one table (FK parents seeded automatically)
124
- lunora seed --seed 42 # reproducible run
126
+ lunora seed --seed 42 --now 1750000000000 # byte-identical run (pin both)
125
127
  lunora seed --reset # wipe local .wrangler/state first (local dev only)
126
128
  lunora seed --dry-run # print NDJSON, write nothing
127
129
  ```
@@ -130,7 +132,7 @@ Also: `--batch-size` (rows per HTTP request, default 500), `--url` (worker URL,
130
132
 
131
133
  ## Limitations
132
134
 
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.
135
+ - **`.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
136
  - **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
137
 
136
138
  > 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
@@ -1,20 +1,20 @@
1
1
  import { Schema } from '@lunora/server';
2
- import { O as OverrideContext } from "./packem_shared/plan.d-BQ6LiWIk.mjs";
3
- export { type S as SeedCounts, type a as SeedOptions, type b as SeedOverrides, type T as TablePlan, s as seedPlan } from "./packem_shared/plan.d-BQ6LiWIk.mjs";
2
+ import { O as OverrideContext } from "./packem_shared/plan.d-CDkBhI-1.mjs";
3
+ export { type S as SeedCounts, type a as SeedOptions, type b as SeedOverrides, type T as TablePlan, s as seedPlan } from "./packem_shared/plan.d-CDkBhI-1.mjs";
4
4
  /**
5
- * A typed, schema-aware seed client — the ergonomic DX layer over {@link seedPlan}.
6
- *
7
- * Where `seedPlan` generates every table at once, the client lets you author one
8
- * table at a time, with autocomplete on each table's columns and live foreign-key
9
- * connection to whatever was seeded earlier this run. Pass your generated
10
- * `InsertModel` type as the type argument — or let `lunora/_generated/seed.ts`
11
- * do that for you — for full type inference on each table's columns.
12
- *
13
- * The client is deterministic (same `seed` ⇒ same rows and ids) and pure unless a
14
- * `persist` hook is supplied, in which case each generated batch is also written
15
- * through it (the test harness, an admin import, …). Because every row carries an
16
- * explicit `_id`, the returned ids are known whether or not anything is persisted.
17
- */
5
+ * A typed, schema-aware seed client — the ergonomic DX layer over {@link seedPlan}.
6
+ *
7
+ * Where `seedPlan` generates every table at once, the client lets you author one
8
+ * table at a time, with autocomplete on each table's columns and live foreign-key
9
+ * connection to whatever was seeded earlier this run. Pass your generated
10
+ * `InsertModel` type as the type argument — or let `lunora/_generated/seed.ts`
11
+ * do that for you — for full type inference on each table's columns.
12
+ *
13
+ * The client is deterministic (same `seed` ⇒ same rows and ids) and pure unless a
14
+ * `persist` hook is supplied, in which case each generated batch is also written
15
+ * through it (the test harness, an admin import, …). Because every row carries an
16
+ * explicit `_id`, the returned ids are known whether or not anything is persisted.
17
+ */
18
18
  /** Picks a concrete count: a fixed number, or a deterministic value within `[min, max]`. */
19
19
  type CountHelper = (countOrRange: number | readonly [number, number]) => number;
20
20
  /** The per-call population spec: a count, an inclusive range, explicit partial rows, or a count callback. */
@@ -23,10 +23,10 @@ type SeedSpec<Row> = number | ReadonlyArray<Partial<Row>> | readonly [number, nu
23
23
  type FieldOverride<Value> = ((context: OverrideContext) => Value) | Value;
24
24
  /** Options for a single table call. `overrides` win over any explicit partial rows. */
25
25
  interface SeedCallOptions<Row> {
26
- overrides?: { [K in keyof Row]?: FieldOverride<Row[K]> };
26
+ overrides?: { [K in keyof Row]?: FieldOverride<Row[K]>; };
27
27
  }
28
28
  /** The id columns the client emits, keyed by table — the result of a single table call. */
29
- type SeedCallResult<InsertModel, Table extends keyof InsertModel> = { [K in Table]: string[] };
29
+ type SeedCallResult<InsertModel, Table extends keyof InsertModel> = { [K in Table]: string[]; };
30
30
  /** The seeder function exposed for one table. */
31
31
  type TableSeeder<InsertModel, Table extends keyof InsertModel> = (spec?: SeedSpec<InsertModel[Table]>, options?: SeedCallOptions<InsertModel[Table]>) => Promise<SeedCallResult<InsertModel, Table>>;
32
32
  /** The `$`-prefixed run state shared by every client. */
@@ -39,26 +39,34 @@ interface SeedClientState {
39
39
  readonly $store: Readonly<Record<string, ReadonlyArray<Record<string, unknown>>>>;
40
40
  }
41
41
  /** The per-table seeder methods plus the `$`-prefixed run state. */
42
- type SeedClient<InsertModel> = SeedClientState & { [Table in keyof InsertModel]: TableSeeder<InsertModel, Table> };
42
+ type SeedClient<InsertModel> = SeedClientState & { [Table in keyof InsertModel]: TableSeeder<InsertModel, Table>; };
43
43
  /** Options for {@link createSeedClient}. */
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`. */
50
58
  seed?: number;
51
59
  }
52
60
  /**
53
- * Create a typed seed client for `schema`. Each table is a method; call it with a
54
- * count, a range, or explicit partial rows, and its foreign keys connect to rows
55
- * seeded earlier this run. State accumulates on `$store`/`$ids` and clears with
56
- * `$reset()`.
57
- * @example
58
- * const seed = createSeedClient<InsertModel>(schema, { seed: 1 });
59
- * const { users } = await seed.users(5);
60
- * const { posts } = await seed.posts((x) => x([10, 20]));
61
- * // posts.authorId values are drawn from `users`.
62
- */
61
+ * Create a typed seed client for `schema`. Each table is a method; call it with a
62
+ * count, a range, or explicit partial rows, and its foreign keys connect to rows
63
+ * seeded earlier this run. State accumulates on `$store`/`$ids` and clears with
64
+ * `$reset()`.
65
+ * @example
66
+ * const seed = createSeedClient<InsertModel>(schema, { seed: 1 });
67
+ * const { users } = await seed.users(5);
68
+ * const { posts } = await seed.posts((x) => x([10, 20]));
69
+ * // posts.authorId values are drawn from `users`.
70
+ */
63
71
  declare const createSeedClient: <InsertModel = Record<string, Record<string, unknown>>>(schema: Schema, options?: SeedClientOptions) => SeedClient<InsertModel>;
64
72
  export { type CountHelper, type OverrideContext, type SeedCallOptions, type SeedCallResult, type SeedClient, type SeedClientOptions, type SeedSpec, createSeedClient };
package/dist/index.d.ts CHANGED
@@ -1,20 +1,20 @@
1
1
  import { Schema } from '@lunora/server';
2
- import { O as OverrideContext } from "./packem_shared/plan.d-BQ6LiWIk.js";
3
- export { type S as SeedCounts, type a as SeedOptions, type b as SeedOverrides, type T as TablePlan, s as seedPlan } from "./packem_shared/plan.d-BQ6LiWIk.js";
2
+ import { O as OverrideContext } from "./packem_shared/plan.d-CDkBhI-1.js";
3
+ export { type S as SeedCounts, type a as SeedOptions, type b as SeedOverrides, type T as TablePlan, s as seedPlan } from "./packem_shared/plan.d-CDkBhI-1.js";
4
4
  /**
5
- * A typed, schema-aware seed client — the ergonomic DX layer over {@link seedPlan}.
6
- *
7
- * Where `seedPlan` generates every table at once, the client lets you author one
8
- * table at a time, with autocomplete on each table's columns and live foreign-key
9
- * connection to whatever was seeded earlier this run. Pass your generated
10
- * `InsertModel` type as the type argument — or let `lunora/_generated/seed.ts`
11
- * do that for you — for full type inference on each table's columns.
12
- *
13
- * The client is deterministic (same `seed` ⇒ same rows and ids) and pure unless a
14
- * `persist` hook is supplied, in which case each generated batch is also written
15
- * through it (the test harness, an admin import, …). Because every row carries an
16
- * explicit `_id`, the returned ids are known whether or not anything is persisted.
17
- */
5
+ * A typed, schema-aware seed client — the ergonomic DX layer over {@link seedPlan}.
6
+ *
7
+ * Where `seedPlan` generates every table at once, the client lets you author one
8
+ * table at a time, with autocomplete on each table's columns and live foreign-key
9
+ * connection to whatever was seeded earlier this run. Pass your generated
10
+ * `InsertModel` type as the type argument — or let `lunora/_generated/seed.ts`
11
+ * do that for you — for full type inference on each table's columns.
12
+ *
13
+ * The client is deterministic (same `seed` ⇒ same rows and ids) and pure unless a
14
+ * `persist` hook is supplied, in which case each generated batch is also written
15
+ * through it (the test harness, an admin import, …). Because every row carries an
16
+ * explicit `_id`, the returned ids are known whether or not anything is persisted.
17
+ */
18
18
  /** Picks a concrete count: a fixed number, or a deterministic value within `[min, max]`. */
19
19
  type CountHelper = (countOrRange: number | readonly [number, number]) => number;
20
20
  /** The per-call population spec: a count, an inclusive range, explicit partial rows, or a count callback. */
@@ -23,10 +23,10 @@ type SeedSpec<Row> = number | ReadonlyArray<Partial<Row>> | readonly [number, nu
23
23
  type FieldOverride<Value> = ((context: OverrideContext) => Value) | Value;
24
24
  /** Options for a single table call. `overrides` win over any explicit partial rows. */
25
25
  interface SeedCallOptions<Row> {
26
- overrides?: { [K in keyof Row]?: FieldOverride<Row[K]> };
26
+ overrides?: { [K in keyof Row]?: FieldOverride<Row[K]>; };
27
27
  }
28
28
  /** The id columns the client emits, keyed by table — the result of a single table call. */
29
- type SeedCallResult<InsertModel, Table extends keyof InsertModel> = { [K in Table]: string[] };
29
+ type SeedCallResult<InsertModel, Table extends keyof InsertModel> = { [K in Table]: string[]; };
30
30
  /** The seeder function exposed for one table. */
31
31
  type TableSeeder<InsertModel, Table extends keyof InsertModel> = (spec?: SeedSpec<InsertModel[Table]>, options?: SeedCallOptions<InsertModel[Table]>) => Promise<SeedCallResult<InsertModel, Table>>;
32
32
  /** The `$`-prefixed run state shared by every client. */
@@ -39,26 +39,34 @@ interface SeedClientState {
39
39
  readonly $store: Readonly<Record<string, ReadonlyArray<Record<string, unknown>>>>;
40
40
  }
41
41
  /** The per-table seeder methods plus the `$`-prefixed run state. */
42
- type SeedClient<InsertModel> = SeedClientState & { [Table in keyof InsertModel]: TableSeeder<InsertModel, Table> };
42
+ type SeedClient<InsertModel> = SeedClientState & { [Table in keyof InsertModel]: TableSeeder<InsertModel, Table>; };
43
43
  /** Options for {@link createSeedClient}. */
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`. */
50
58
  seed?: number;
51
59
  }
52
60
  /**
53
- * Create a typed seed client for `schema`. Each table is a method; call it with a
54
- * count, a range, or explicit partial rows, and its foreign keys connect to rows
55
- * seeded earlier this run. State accumulates on `$store`/`$ids` and clears with
56
- * `$reset()`.
57
- * @example
58
- * const seed = createSeedClient<InsertModel>(schema, { seed: 1 });
59
- * const { users } = await seed.users(5);
60
- * const { posts } = await seed.posts((x) => x([10, 20]));
61
- * // posts.authorId values are drawn from `users`.
62
- */
61
+ * Create a typed seed client for `schema`. Each table is a method; call it with a
62
+ * count, a range, or explicit partial rows, and its foreign keys connect to rows
63
+ * seeded earlier this run. State accumulates on `$store`/`$ids` and clears with
64
+ * `$reset()`.
65
+ * @example
66
+ * const seed = createSeedClient<InsertModel>(schema, { seed: 1 });
67
+ * const { users } = await seed.users(5);
68
+ * const { posts } = await seed.posts((x) => x([10, 20]));
69
+ * // posts.authorId values are drawn from `users`.
70
+ */
63
71
  declare const createSeedClient: <InsertModel = Record<string, Record<string, unknown>>>(schema: Schema, options?: SeedClientOptions) => SeedClient<InsertModel>;
64
72
  export { type CountHelper, type OverrideContext, type SeedCallOptions, type SeedCallResult, type SeedClient, type SeedClientOptions, type SeedSpec, createSeedClient };
package/dist/index.mjs CHANGED
@@ -1,2 +1 @@
1
- export { createSeedClient } from './packem_shared/createSeedClient-CI9LRLTi.mjs';
2
- export { s as seedPlan } from './packem_shared/plan-DirmkctQ.mjs';
1
+ import{createSeedClient as o}from"./packem_shared/createSeedClient-DhUqTKW6.mjs";import{s as a}from"./packem_shared/plan-DDkqfb3s.mjs";export{o as createSeedClient,a as seedPlan};
@@ -0,0 +1 @@
1
+ import{a as $,s as C,c as P}from"./plan-DDkqfb3s.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 y}from"@lunora/errors";import{faker as h}from"@faker-js/faker";import{optionalInner as K}from"@lunora/values";let O=0;const me=(e,n)=>e<n?-1:e>n?1:0,L=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(t=>L(t)).join(",")}]`:`{${Object.keys(e).toSorted(me).map(t=>`${JSON.stringify(t)}:${L(e[t])}`).join(",")}}`,Z=(e,n)=>{let t=3735928559^n,r=1103547991^n;for(let o=0;o<e.length;o+=1){const a=e.codePointAt(o)??0;t=Math.imul(t^a,2654435761),r=Math.imul(r^a,1597334677)}return t=Math.imul(t^t>>>16,2246822507),t^=Math.imul(r^r>>>13,3266489909),r=Math.imul(r^r>>>16,2246822507),r^=Math.imul(t^t>>>13,3266489909),4294967296*(2097151&r)+(t>>>0)},de="\0",he=e=>{const n=typeof e=="string"?de+e:L(e);return Z(n,O)%4294967296},fe=e=>{if(typeof e=="number"){O=e>>>0;return}if(typeof e=="string"){O=Z(e,0)%4294967296;return}let n=0;for(const t of e)n=Math.imul(n^t,2654435761)>>>0;O=n},f=(e,n)=>(h.seed(he(e)),n()),ge=(e,n)=>{if(typeof n=="number")return n;const[t,r]=n;return f(["__count__",e],()=>h.number.int({max:r,min:t}))},pe="example.com",ve=/[a-z]/,we=/[A-Z]/,be=/\d/,u={bool(e){return f(e,()=>h.datatype.boolean())},city(e){return f(e,()=>h.location.city())},country(e){return f(e,()=>h.location.country())},email(e){return f(e,()=>h.internet.email({provider:pe}).toLowerCase())},firstName(e){return f(e,()=>h.person.firstName())},float(e,n){const{fractionDigits:t=2,max:r=1e3,min:o=0}=n??{};return f(e,()=>h.number.float({fractionDigits:t,max:r,min:o}))},fullName(e){return f(e,()=>h.person.fullName())},int(e,n){const{max:t=1e3,min:r=0}=n??{};return f(e,()=>h.number.int({max:t,min:r}))},lastName(e){return f(e,()=>h.person.lastName())},oneOf(e,n){if(n.length!==0)return f(e,()=>h.helpers.arrayElement(n))},paragraph(e){return f(e,()=>h.lorem.paragraph())},password(e){return f(e,()=>h.internet.password())},phoneNumber(e){return f(e,()=>h.phone.number())},scramble(e,n){const t=new Set(n?.preserve);return Array.from({length:e.length},(r,o)=>{const a=e.charAt(o);return t.has(a)?a:ve.test(a)?f([e,o,"l"],()=>h.string.alpha({casing:"lower",length:1})):we.test(a)?f([e,o,"u"],()=>h.string.alpha({casing:"upper",length:1})):be.test(a)?f([e,o,"d"],()=>String(h.number.int({max:9,min:0}))):a}).join("")},sentence(e,n){return f(e,()=>h.lorem.sentence(n?{max:n.max??8,min:n.min??3}:void 0))},slug(e){return f(e,()=>h.lorem.slug())},streetAddress(e){return f(e,()=>h.location.streetAddress())},times(e,n,t){const r=ge(e,n),o=[];for(let a=0;a<r;a+=1)o.push(t([e,a]));return o},url(e){return f(e,()=>h.internet.url())},username(e){return f(e,()=>h.internet.username())},uuid(e){return f(e,()=>h.string.uuid())},word(e){return f(e,()=>h.lorem.word())}},p=e=>e._meta??{},X=e=>K(e)??e,ee=e=>{const{column:n}=p(e);if(n?.defaultValue!==void 0||n?.defaultFn!==void 0)return!0;const t=K(e);return t===void 0?!1:ee(t)},ye=(e,n)=>{const t=X(n),r=p(t);return{fkTable:t.kind==="id"?r.tableName:void 0,hasServerDefault:ee(n),kind:t.kind,name:e,nullable:r.column?.notNull===!1,optional:n.kind==="optional",unique:r.column?.unique===!0,validator:t}},Se=e=>{const{tables:n}=e;return Object.entries(n).map(([t,r])=>({fields:Object.entries(r.shape).map(([o,a])=>ye(o,a)),name:t}))},Ae=(e,n)=>{const t=new Map(e.map(s=>[s.name,s])),r=s=>{const c=t.get(s);if(c===void 0)return new Set;const l=new Set;for(const d of c.fields)d.fkTable!==void 0&&d.fkTable!==s&&n.has(d.fkTable)&&l.add(d.fkTable);return l},o=[],a=new Set,i=[...n].filter(s=>t.has(s));for(;i.length>0;){const s=i.findIndex(d=>[...r(d)].every(g=>a.has(g))),c=s===-1?0:s,[l]=i.splice(c,1);if(l===void 0)break;o.push(l),a.add(l)}return o},xe=(e,n,t=new Set)=>{const r=new Map(e.map(i=>[i.name,i])),o=new Set(n),a=[...o];for(;a.length>0;){const i=a.pop();if(i===void 0)break;const s=r.get(i);if(s!==void 0&&!t.has(i))for(const c of s.fields)c.fkTable!==void 0&&c.fkTable!==i&&r.has(c.fkTable)&&!o.has(c.fkTable)&&(o.add(c.fkTable),a.push(c.fkTable))}return o},ne=e=>p(e).constraints??{},Te=[{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"]}],Ie=new Set(["at","date","deadline","expires","expiry","since","timestamp","until"]),ke=/([a-z\d])([A-Z])/gu,$e=/[\s_-]+/u,Oe=e=>e.replaceAll(ke,"$1 $2").split($e).filter(n=>n!=="").map(n=>n.toLowerCase()),te={max:1e3,min:0},re={max:1e6,min:0},Ne=4320*60*60*1e3,P=(e,n,t)=>{const{maximum:r,minimum:o}=e;if(r!==void 0&&o!==void 0&&o>r)throw new y("INTERNAL",`Seed constraint error for field "${t}": minimum (${String(o)}) > maximum (${String(r)}). Adjust the schema constraints.`);const a=n.max-n.min;return{max:r??(o!==void 0&&o>n.max?o+a:n.max),min:o??(r!==void 0&&r<n.min?r-a:n.min)}},oe=e=>{const t=Oe(e).at(-1);return t!==void 0&&Ie.has(t)},G=(e,n)=>n-u.int(e,{max:Ne,min:0}),N=(e,n)=>{throw new y("INTERNAL",`@lunora/seed: column "${e}" ${n}. Supply a value via \`overrides\` (\`{ <table>: { ${e}: … } }\`) or restrict the run with \`only\` so the table is skipped.`)},q=(e,n)=>{const t=e===""?"x":e,r=Math.min(Math.max(t.length,n.min),n.max);return t.padEnd(r,t).slice(0,r)},T="@example.com",_="https://",_e=/[\s@]/gu,Ee=/[^a-z\d]/gu,Me=(e,n)=>{const t=e.lastIndexOf("@"),r=t>0?e.slice(t):"",o=r.length>0&&r.length<n.max?r:T,a=(t>0?e.slice(0,t):e).replaceAll(_e,"");return`${q(a,{max:n.max-o.length,min:Math.max(1,n.min-o.length)})}${o}`},De=(e,n)=>{const t=URL.canParse(e)?new URL(e).hostname.replaceAll(Ee,""):"";return`${_}${q(t,{max:n.max-_.length,min:Math.max(1,n.min-_.length)})}`},Re={email:{fit:Me,generate:e=>u.email(e),minimum:T.length+1},uri:{fit:De,generate:e=>u.url(e),minimum:_.length+1}},Le=(e,n)=>{const t=e.toLowerCase(),r=Te.find(o=>o.keywords.some(a=>t.includes(a)));return r===void 0?u.word(n):r.generate(n)},Pe=(e,n,t)=>{const{format:r,maxLength:o,minLength:a,pattern:i}=t;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&&a!==void 0&&a>o)throw new y("INTERNAL",`Seed constraint error for field "${e}": minLength (${String(a)}) > maxLength (${String(o)}). Adjust the schema constraints.`);const s={max:o??Number.POSITIVE_INFINITY,min:a??0};if(r===void 0)return q(Le(e,n),s);const c=Re[r];if(c===void 0)return N(e,`declares format "${r}", which the seeder has no generator for`);if(s.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(n);return l.length>=s.min&&l.length<=s.max?l:c.fit(l,s)},b=(e,n,t,r)=>{const o=X(e),a=ne(o);if(a.enum!==void 0&&a.enum.length>0)return u.oneOf(t,a.enum);switch(o.kind){case"any":return u.word(t);case"array":{const i=p(o).inner;return i===void 0?[]:u.times(t,[1,3],s=>b(i,n,s,r))}case"bigint":return u.int(t,P(a,re,n));case"boolean":return u.bool(t);case"bytes":return Array.from({length:8},(i,s)=>u.int([t,s],{max:255,min:0}));case"date":case"timestamp":return G(t,r);case"from":return N(n,"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(t);case"literal":return p(o).value;case"null":return null;case"number":{if(a.maximum===void 0&&a.minimum===void 0&&oe(n))return G(t,r);const{max:i,min:s}=P(a,te,n);return!Number.isInteger(s)||!Number.isInteger(i)?u.float(t,{max:i,min:s}):u.int(t,{max:i,min:s})}case"object":{const i=p(o).shape??{};return Object.fromEntries(Object.entries(i).map(([s,c])=>[s,b(c,s,[t,s],r)]))}case"record":{const{keyValidator:i,valueValidator:s}=p(o),c=u.times(t,[1,3],l=>{const d=i===void 0?u.word(["k",l]):String(b(i,n,["k",l],r)),g=s===void 0?u.word(["v",l]):b(s,n,["v",l],r);return[d,g]});return Object.fromEntries(c)}case"storage":return`seed/${u.uuid(t)}`;case"string":return Pe(n,t,a);case"union":{const i=p(o).members??[],s=u.oneOf(t,i);return s===void 0?u.word(t):b(s,n,[t,"u"],r)}default:return u.word(t)}},R=1e6,qe=6e4,je=2**32,Ce=(e,n,t)=>{const r=[...e];for(let o=r.length-1;o>0;o-=1){const a=u.int([n,t,"unique-deal",o],{max:o,min:0}),i=r[o];r[o]=r[a],r[a]=i}return r},Ue=(e,n,t)=>{const{maxLength:r}=t,o=String(n),a=e.indexOf("@");if(a>0){const c=e.slice(0,a),l=e.slice(a),d=r===void 0?c.length:r-l.length-o.length-1;if(d>=0)return`${c.slice(0,d)}+${o}${l}`}if(t.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}`,s=r===void 0?e.length:Math.max(0,r-i.length);return`${e.slice(0,s)}${i}`},E=(e,n,t)=>{const r=Ce(e,n,t);return{capacity:r.length,valueAt:o=>r[o%r.length]}},Fe=(e,n)=>{if(Number.isInteger(e)&&Number.isInteger(n)){const t=n-e+1;return{capacity:t,valueAt:r=>e+r%t}}return{capacity:R,valueAt:t=>e+t%R*(n-e)/R}},H=e=>({capacity:Number.POSITIVE_INFINITY,valueAt:n=>e-n*qe}),Ve={capacity:Number.POSITIVE_INFINITY,valueAt:e=>e},W={capacity:Number.POSITIVE_INFINITY,valueAt:(e,n)=>n()},z=(e,n,t)=>{if(e.maximum===void 0&&e.minimum===void 0)return Ve;const{max:r,min:o}=P(e,n,t);return Fe(o,r)},Q=(e,n,t)=>{throw new y("INTERNAL",`@lunora/seed: cannot generate unique values for "${e}"."${n}" — ${t}. Supply them via \`overrides\` (\`{ ${e}: { ${n}: … } }\`) or drop \`.unique()\` from the column.`)},Be=({format:e,maxLength:n})=>{if(n===void 0)return Number.POSITIVE_INFINITY;const t=e==="email"?T.length:1;return n<=t?0:10**(n-t)},Ye=(e,n,t)=>(e.pattern!==void 0&&Q(n,t,"the column is pattern-constrained, and an index-tagged value would no longer match it"),e.format!==void 0&&e.format!=="email"&&Q(n,t,`the column declares format "${e.format}", which an index-tagged value would no longer satisfy`),{capacity:Be(e),valueAt:(r,o)=>Ue(String(o()),r,e)}),Ge=(e,n,t)=>E(e,n,t),He=(e,n)=>{const{now:t,table:r}=n,o=ne(e.validator),a=o.enum;if(Array.isArray(a)&&a.length>0)return E(a,r,e.name);switch(e.kind){case"any":case"string":return Ye(o,r,e.name);case"bigint":return z(o,re,e.name);case"boolean":return E([!1,!0],r,e.name);case"bytes":return{capacity:je,valueAt:i=>[Math.floor(i/16777216)%256,Math.floor(i/65536)%256,Math.floor(i/256)%256,i%256,...Array.from({length:4},(s,c)=>u.int([r,e.name,"unique-bytes",c],{max:255,min:0}))]};case"date":case"timestamp":return H(t);case"literal":return{capacity:1,valueAt:()=>p(e.validator).value};case"number":return o.maximum===void 0&&o.minimum===void 0&&oe(e.name)?H(t):z(o,te,e.name);case"union":{const i=p(e.validator).members??[],s=i.filter(c=>c.kind==="literal").map(c=>p(c).value);return s.length>0&&s.length===i.length?E(s,r,e.name):W}default:return W}},We=(e,n)=>typeof e=="function"?e(n):e,ze=(e,n)=>{if(!e.optional)return e.nullable?null:u.uuid(n)},ae=(e,n,t,r,o)=>{const a=e.fkTable,i=a===n?(r.get(n)??[]).slice(0,t):r.get(a)??[],s=o[a]??[];return s.length===0?i:[...i,...s]},Qe=(e,n,t,r)=>{const o=[...r[e]??[],...(t.get(e)??[]).slice(0,n)];return o[n]??o.at(-1)},J=(e,n,t,r)=>[e,n,t,r],Je=(e,n)=>{const{existingIds:t,idsByTable:r,index:o,input:a,localIndex:i,now:s,table:c,uniqueDeals:l}=n;if(e.fkTable!==void 0){const g=ae(e,c,i,r,t);if(g.length===0)return ze(e,a);if(!e.unique)return u.oneOf(a,g);const I=l.get(e.name);return I===void 0?Qe(c,i,r,t):I.valueAt(o,()=>u.oneOf(a,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,a,s)):b(e.validator,e.name,a,s)},Ke=(e,n,t,r)=>{if(!(t(e).length===0||Object.hasOwn(r,e.name)))throw new y("BAD_REQUEST",`cannot seed into the non-empty table "${n}": unique self-referencing column "${e.name}" cannot be dealt across calls, because the values already stored in it are not knowable from the row ids. Seed the table in one call, or supply "${e.name}" yourself.`)},Ze=(e,n,t,r,o,a,i)=>{const s=new Map;for(const c of e){if(!c.unique||c.hasServerDefault&&c.fkTable===void 0)continue;if(c.fkTable===n){Ke(c,n,i,a);continue}const l=c.fkTable===void 0?void 0:i(c);if(l?.length===0)continue;const d=l===void 0?He(c,{now:o,table:n}):Ge(l,n,c.name),g=r+t;if(g>d.capacity&&!Object.hasOwn(a,c.name))throw new y("BAD_REQUEST",`cannot seed ${String(g)} rows into "${n}": unique column "${c.name}" has only ${String(d.capacity)} possible values`);s.set(c.name,d)}return s},tn=(e,n={})=>{const{counts:t={},defaultCount:r=10,existingIds:o={},indexOffset:a={},now:i=Date.now(),only:s,overrides:c={},seed:l=0}=n;fe(l);const d=Se(e);if(s!==void 0){const m=new Set(d.map(w=>w.name));for(const w of s)if(!m.has(w)){const A=d.map(k=>k.name).join(", ");throw new y("BAD_REQUEST",`unknown table "${w}" in seed \`only\` — schema defines: ${A||"(no tables)"}`)}}const g=new Set(s??d.map(m=>m.name)),I=new Set(Object.keys(o).filter(m=>!g.has(m)&&(o[m]??[]).length>0)),se=new Set([...xe(d,g,I)].filter(m=>g.has(m)||(o[m]??[]).length===0)),ie=Ae(d,se),ce=new Map(d.map(m=>[m.name,m])),M=new Map,j={},C=[];for(const m of ie){const w=ce.get(m);if(w===void 0)continue;const A=c[m]??{},k=t[m]??r,U=a[m]??0,ue=Ze(w.fields,m,k,U,i,A,S=>ae(S,m,0,M,o)),F=[];M.set(m,F);const D=[];j[m]=D;for(let S=0;S<k;S+=1){const $=U+S,x={},V=(v,le)=>{if(Object.hasOwn(A,v)){const Y=We(A[v],{field:v,index:$,row:x,store:j,table:m});if(Y!==void 0){x[v]=Y;return}}const B=le();B!==void 0&&(x[v]=B)};V("_id",()=>u.uuid(J(l,m,$,"_id"))),F.push(x._id);for(const v of w.fields)V(v.name,()=>Je(v,{existingIds:o,idsByTable:M,index:$,input:J(l,m,$,v.name),localIndex:S,now:i,table:m,uniqueDeals:ue}));D.push(x)}C.push({rows:D,table:m})}return C};export{fe as a,u as c,Se as i,tn as s};
@@ -0,0 +1,88 @@
1
+ import { Schema } from '@lunora/server';
2
+ /**
3
+ * The pure, I/O-free core of seeding. {@link seedPlan} introspects a schema,
4
+ * generates rows table-by-table in foreign-key order, and resolves every
5
+ * `v.id("parent")` column to a real id of an already-generated parent row. The
6
+ * result feeds every adapter (test harness, CLI, studio) — none of them
7
+ * re-implement generation.
8
+ *
9
+ * Determinism: a `seed` value selects the global copycat mapping, and each value
10
+ * is hashed from `[seed, table, index, field]`, so the same `(schema, options)`
11
+ * always yields byte-identical rows.
12
+ */
13
+ /** A row context handed to an override function. */
14
+ interface OverrideContext {
15
+ field: string;
16
+ /** The row's absolute index (the `indexOffset` base plus its position in this batch). */
17
+ index: number;
18
+ /** The row built so far (system `_id` first, then earlier fields). */
19
+ row: Record<string, unknown>;
20
+ /**
21
+ * A live, read-only view of every table's rows generated so far this run,
22
+ * keyed by table name. Lets an override correlate across tables (e.g. copy a
23
+ * field from the parent row a foreign key points at). Rows for the current
24
+ * table accumulate as they are built, so only earlier rows are visible.
25
+ */
26
+ store: Readonly<Record<string, ReadonlyArray<Record<string, unknown>>>>;
27
+ table: string;
28
+ }
29
+ /**
30
+ * Per-table, per-field overrides. Each override is a static value or a function
31
+ * of the row context; field `_id` overrides the generated primary key.
32
+ */
33
+ type SeedOverrides = Record<string, Record<string, unknown>>;
34
+ /** Per-table row counts. */
35
+ type SeedCounts = Record<string, number>;
36
+ interface SeedOptions {
37
+ /** Rows per table; falls back to {@link SeedOptions.defaultCount} when a table is absent. */
38
+ counts?: SeedCounts;
39
+ /** Count used for any selected table not present in `counts` (default `10`). */
40
+ defaultCount?: number;
41
+ /**
42
+ * Ids of rows that already exist in the target store, keyed by table. Foreign
43
+ * keys may resolve to these in addition to freshly-seeded parents, and a
44
+ * parent table fully covered here is not re-seeded when it is only pulled in
45
+ * as an FK dependency (it is when named explicitly in `only`).
46
+ */
47
+ existingIds?: Readonly<Record<string, ReadonlyArray<string>>>;
48
+ /**
49
+ * Per-table absolute index base for generation. Defaults to `0`. A client
50
+ * seeding the same table across several calls passes the running total so
51
+ * each batch hashes from fresh indices and never collides ids with an
52
+ * earlier batch.
53
+ */
54
+ indexOffset?: Readonly<Record<string, number>>;
55
+ /**
56
+ * Wall-clock reference for time-valued columns (`createdAt`, `expiresAt`, …),
57
+ * as epoch-ms. Defaults to now.
58
+ *
59
+ * Pin it to make a plan byte-for-byte reproducible: seeding is deterministic
60
+ * in `seed` alone for every other column, and this is the one input that
61
+ * would otherwise drift between runs. A pinned `(seed, now)` pair is what
62
+ * makes a seeded screenshot, test fixture, or bug report replayable.
63
+ */
64
+ now?: number;
65
+ /**
66
+ * Restrict seeding to these tables. Transitive `v.id(...)` parents are added
67
+ * automatically (unless already covered by `existingIds`) so child foreign
68
+ * keys resolve to real rows. The result is still ordered by FK dependency.
69
+ * Default: all tables.
70
+ */
71
+ only?: ReadonlyArray<string>;
72
+ /** Static values or functions overriding generated columns. */
73
+ overrides?: SeedOverrides;
74
+ /** Deterministic mapping selector — same seed ⇒ same rows. Default `0`. */
75
+ seed?: number;
76
+ }
77
+ /** One table's generated rows, in insert order. Each row carries an explicit `_id`. */
78
+ interface TablePlan {
79
+ rows: ReadonlyArray<Record<string, unknown>>;
80
+ table: string;
81
+ }
82
+ /**
83
+ * Build a deterministic, FK-consistent set of rows for `schema`.
84
+ * @returns one {@link TablePlan} per seeded table, ordered so a table's FK
85
+ * parents come before it.
86
+ */
87
+ declare const seedPlan: (schema: Schema, options?: SeedOptions) => ReadonlyArray<TablePlan>;
88
+ export { OverrideContext as O, SeedCounts as S, TablePlan as T, SeedOptions as a, SeedOverrides as b, seedPlan as s };
@@ -0,0 +1,88 @@
1
+ import { Schema } from '@lunora/server';
2
+ /**
3
+ * The pure, I/O-free core of seeding. {@link seedPlan} introspects a schema,
4
+ * generates rows table-by-table in foreign-key order, and resolves every
5
+ * `v.id("parent")` column to a real id of an already-generated parent row. The
6
+ * result feeds every adapter (test harness, CLI, studio) — none of them
7
+ * re-implement generation.
8
+ *
9
+ * Determinism: a `seed` value selects the global copycat mapping, and each value
10
+ * is hashed from `[seed, table, index, field]`, so the same `(schema, options)`
11
+ * always yields byte-identical rows.
12
+ */
13
+ /** A row context handed to an override function. */
14
+ interface OverrideContext {
15
+ field: string;
16
+ /** The row's absolute index (the `indexOffset` base plus its position in this batch). */
17
+ index: number;
18
+ /** The row built so far (system `_id` first, then earlier fields). */
19
+ row: Record<string, unknown>;
20
+ /**
21
+ * A live, read-only view of every table's rows generated so far this run,
22
+ * keyed by table name. Lets an override correlate across tables (e.g. copy a
23
+ * field from the parent row a foreign key points at). Rows for the current
24
+ * table accumulate as they are built, so only earlier rows are visible.
25
+ */
26
+ store: Readonly<Record<string, ReadonlyArray<Record<string, unknown>>>>;
27
+ table: string;
28
+ }
29
+ /**
30
+ * Per-table, per-field overrides. Each override is a static value or a function
31
+ * of the row context; field `_id` overrides the generated primary key.
32
+ */
33
+ type SeedOverrides = Record<string, Record<string, unknown>>;
34
+ /** Per-table row counts. */
35
+ type SeedCounts = Record<string, number>;
36
+ interface SeedOptions {
37
+ /** Rows per table; falls back to {@link SeedOptions.defaultCount} when a table is absent. */
38
+ counts?: SeedCounts;
39
+ /** Count used for any selected table not present in `counts` (default `10`). */
40
+ defaultCount?: number;
41
+ /**
42
+ * Ids of rows that already exist in the target store, keyed by table. Foreign
43
+ * keys may resolve to these in addition to freshly-seeded parents, and a
44
+ * parent table fully covered here is not re-seeded when it is only pulled in
45
+ * as an FK dependency (it is when named explicitly in `only`).
46
+ */
47
+ existingIds?: Readonly<Record<string, ReadonlyArray<string>>>;
48
+ /**
49
+ * Per-table absolute index base for generation. Defaults to `0`. A client
50
+ * seeding the same table across several calls passes the running total so
51
+ * each batch hashes from fresh indices and never collides ids with an
52
+ * earlier batch.
53
+ */
54
+ indexOffset?: Readonly<Record<string, number>>;
55
+ /**
56
+ * Wall-clock reference for time-valued columns (`createdAt`, `expiresAt`, …),
57
+ * as epoch-ms. Defaults to now.
58
+ *
59
+ * Pin it to make a plan byte-for-byte reproducible: seeding is deterministic
60
+ * in `seed` alone for every other column, and this is the one input that
61
+ * would otherwise drift between runs. A pinned `(seed, now)` pair is what
62
+ * makes a seeded screenshot, test fixture, or bug report replayable.
63
+ */
64
+ now?: number;
65
+ /**
66
+ * Restrict seeding to these tables. Transitive `v.id(...)` parents are added
67
+ * automatically (unless already covered by `existingIds`) so child foreign
68
+ * keys resolve to real rows. The result is still ordered by FK dependency.
69
+ * Default: all tables.
70
+ */
71
+ only?: ReadonlyArray<string>;
72
+ /** Static values or functions overriding generated columns. */
73
+ overrides?: SeedOverrides;
74
+ /** Deterministic mapping selector — same seed ⇒ same rows. Default `0`. */
75
+ seed?: number;
76
+ }
77
+ /** One table's generated rows, in insert order. Each row carries an explicit `_id`. */
78
+ interface TablePlan {
79
+ rows: ReadonlyArray<Record<string, unknown>>;
80
+ table: string;
81
+ }
82
+ /**
83
+ * Build a deterministic, FK-consistent set of rows for `schema`.
84
+ * @returns one {@link TablePlan} per seeded table, ordered so a table's FK
85
+ * parents come before it.
86
+ */
87
+ declare const seedPlan: (schema: Schema, options?: SeedOptions) => ReadonlyArray<TablePlan>;
88
+ export { OverrideContext as O, SeedCounts as S, TablePlan as T, SeedOptions as a, SeedOverrides as b, seedPlan as s };
@@ -0,0 +1 @@
1
+ import{s as a}from"./plan-DDkqfb3s.mjs";export{a as seedPlan};
@@ -1,5 +1,5 @@
1
1
  import { Schema } from '@lunora/server';
2
2
  import { TestHarness } from '@lunora/testing';
3
- import { a as SeedOptions } from "./packem_shared/plan.d-BQ6LiWIk.mjs";
3
+ import { a as SeedOptions } from "./packem_shared/plan.d-CDkBhI-1.mjs";
4
4
  declare const seed: (harness: TestHarness, schema: Schema, options?: SeedOptions) => Promise<Record<string, string[]>>;
5
5
  export { seed };
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Schema } from '@lunora/server';
2
2
  import { TestHarness } from '@lunora/testing';
3
- import { a as SeedOptions } from "./packem_shared/plan.d-BQ6LiWIk.js";
3
+ import { a as SeedOptions } from "./packem_shared/plan.d-CDkBhI-1.js";
4
4
  declare const seed: (harness: TestHarness, schema: Schema, options?: SeedOptions) => Promise<Record<string, string[]>>;
5
5
  export { seed };
package/dist/testing.mjs CHANGED
@@ -1,53 +1 @@
1
- import { s as seedPlan, i as introspectSchema } from './packem_shared/plan-DirmkctQ.mjs';
2
-
3
- const reviveRow = (row, bigintFields, bytesFields) => {
4
- if (bigintFields.size === 0 && bytesFields.size === 0) {
5
- return row;
6
- }
7
- const revived = { ...row };
8
- for (const field of bigintFields) {
9
- const value = revived[field];
10
- if (typeof value === "number") {
11
- revived[field] = BigInt(value);
12
- }
13
- }
14
- for (const field of bytesFields) {
15
- const value = revived[field];
16
- if (Array.isArray(value)) {
17
- revived[field] = Uint8Array.from(value).buffer;
18
- }
19
- }
20
- return revived;
21
- };
22
- const seed = async (harness, schema, options = {}) => {
23
- const plan = seedPlan(schema, options);
24
- const specs = introspectSchema(schema);
25
- const bigintFieldsByTable = /* @__PURE__ */ new Map();
26
- const bytesFieldsByTable = /* @__PURE__ */ new Map();
27
- for (const spec of specs) {
28
- const bigintFields = new Set(spec.fields.filter((field) => field.kind === "bigint").map((field) => field.name));
29
- const bytesFields = new Set(spec.fields.filter((field) => field.kind === "bytes").map((field) => field.name));
30
- if (bigintFields.size > 0) {
31
- bigintFieldsByTable.set(spec.name, bigintFields);
32
- }
33
- if (bytesFields.size > 0) {
34
- bytesFieldsByTable.set(spec.name, bytesFields);
35
- }
36
- }
37
- const ids = {};
38
- await harness.run(async (context) => {
39
- const insert = context.db.insert;
40
- for (const { rows, table } of plan) {
41
- const tableIds = [];
42
- const bigintFields = bigintFieldsByTable.get(table) ?? /* @__PURE__ */ new Set();
43
- const bytesFields = bytesFieldsByTable.get(table) ?? /* @__PURE__ */ new Set();
44
- for (const row of rows) {
45
- tableIds.push(await insert(table, reviveRow(row, bigintFields, bytesFields), { allowExplicitId: true }));
46
- }
47
- ids[table] = tableIds;
48
- }
49
- });
50
- return ids;
51
- };
52
-
53
- export { seed };
1
+ import{s as m,i as u}from"./packem_shared/plan-DDkqfb3s.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.13",
3
+ "version": "1.0.0-alpha.130",
4
4
  "description": "Schema-driven, deterministic database seeding for Lunora: realistic fake data from defineSchema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -47,11 +47,13 @@
47
47
  "access": "public"
48
48
  },
49
49
  "dependencies": {
50
- "@faker-js/faker": "^10.5.0"
50
+ "@faker-js/faker": "10.5.0",
51
+ "@lunora/errors": "1.0.0-alpha.36"
51
52
  },
52
53
  "peerDependencies": {
53
- "@lunora/server": "1.0.0-alpha.13",
54
- "@lunora/values": "1.0.0-alpha.3"
54
+ "@lunora/server": ">=1.0.0-alpha.24 <2.0.0-0",
55
+ "@lunora/testing": ">=1.0.0-alpha.131 <2.0.0-0",
56
+ "@lunora/values": ">=1.0.0-alpha.7 <2.0.0-0"
55
57
  },
56
58
  "engines": {
57
59
  "node": "^22.15.0 || >=24.11.0"