@frigear-nu/module-kit 0.0.3 → 0.0.5

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
@@ -7,51 +7,166 @@ A simple nuxt module to simplify development of other nuxt modules within frigea
7
7
  - Be a shared module kit for building nuxt modules within frigear – as generic as possible within the frigear requirements
8
8
  - E.g building a 'user' or 'blog' or 'post' module for frigear-nu
9
9
 
10
- ## Extending a database table
10
+ ## Utils & composables
11
11
 
12
- Modules can register a table schema using `extendDatabaseTable` from `@frigear-nu/module-kit/utils`:
12
+ All of the helpers below are exported from `@frigear-nu/module-kit/utils`.
13
+
14
+ ### Module helpers
15
+
16
+ #### `registerModule`
17
+
18
+ Registers a module name against `nuxt.options.moduleKit._modules`, de-duplicating entries. Useful during a module's
19
+ `setup()` to keep track of which Frigear modules are active in the current Nuxt app:
13
20
 
14
21
  ```ts
15
- extendDatabaseTable(nuxt, {
22
+ import { registerModule } from '@frigear-nu/module-kit/utils'
23
+
24
+ registerModule('@frigear-nu/users', nuxt)
25
+ ```
26
+
27
+ #### `defineModulePage`
28
+
29
+ Tags a `NuxtPage` with the owning module's name in `page.meta.moduleName`, so pages registered by a module can be
30
+ identified later (e.g. for navigation or permission checks):
31
+
32
+ ```ts
33
+ import { defineModulePage } from '@frigear-nu/module-kit/utils'
34
+
35
+ extendPages((pages) => {
36
+ pages.push(defineModulePage('@frigear-nu/users', {
37
+ name: 'users-profile',
38
+ path: '/users/profile',
39
+ file: resolver.resolve('./runtime/pages/profile.vue'),
40
+ }))
41
+ })
42
+ ```
43
+
44
+ ### URL helpers
45
+
46
+ #### `moduleUrl` and `moduleApiUrl`
47
+
48
+ Build consistent, kebab-cased URLs for a module's pages and API routes. If `path` starts with `/` it is treated as
49
+ absolute and the module name is not prefixed:
50
+
51
+ ```ts
52
+ import { moduleUrl, moduleApiUrl } from '@frigear-nu/module-kit/utils'
53
+
54
+ moduleUrl('OAuthServer') // => '/o-auth-server'
55
+ moduleUrl('OAuthServer', 'login') // => '/o-auth-server/login'
56
+ moduleApiUrl('OAuthServer', 'token') // => '/api/o-auth-server/token'
57
+ ```
58
+
59
+ ### Database schema helpers
60
+
61
+ These helpers let one module own a Drizzle table while letting other modules (and host applications) extend it with
62
+ additional columns, without the owning module needing to know about them.
63
+
64
+ #### `registerDatabaseTable`
65
+
66
+ Modules register a table schema using `registerDatabaseTable`. The simplest usage needs no wrapper at all — `schema`
67
+ can be a *regular* drizzle file:
68
+
69
+ ```ts
70
+ // runtime/db/schema.sqlite.ts
71
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
72
+
73
+ export const users = sqliteTable('users', {
74
+ id: integer().primaryKey({ autoIncrement: true }),
75
+ email: text().notNull().unique(),
76
+ })
77
+ ```
78
+
79
+ ```ts
80
+ import { registerDatabaseTable } from '@frigear-nu/module-kit/utils'
81
+
82
+ registerDatabaseTable(nuxt, {
16
83
  name: 'users',
17
- factory: 'defineUsersTable',
18
84
  resolver: createResolver(import.meta.url),
19
85
  schema: dialect => `./runtime/db/schema.${dialect}`,
20
- override: options.settings?.schemas?.users,
86
+ })
87
+ ```
88
+
89
+ #### `extendDatabaseTable`
90
+
91
+ Other modules can register column files during setup. These can be regular files too — either a full table
92
+ definition or a plain columns object, both without any helper wrapper:
93
+
94
+ ```ts
95
+ import { extendDatabaseTable } from '@frigear-nu/module-kit/utils'
96
+
97
+ extendDatabaseTable(nuxt, {
98
+ name: 'users',
99
+ resolver: createResolver(import.meta.url),
100
+ file: dialect => `./runtime/db/extra.${dialect}`,
21
101
  })
22
102
  ```
23
103
 
24
104
  ```ts
25
- import { ColumnBuilderBase } from "drizzle-orm";
105
+ // runtime/db/extra.sqlite.ts
106
+ import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
26
107
 
27
- const defineUsersTable = (extras: Record<string, ColumnBuilderBase>) => {
28
- return sqliteTable('users', {
108
+ export const users = sqliteTable('users', {
109
+ locale: text().default('da'),
110
+ })
111
+ ```
112
+
113
+ Module-kit parses each file's exported table (or plain object) matching `name`, and merges their columns directly —
114
+ later registrations override columns from earlier ones with the same key — before generating a single `sqliteTable(...)`
115
+ call with the merged columns and deduplicated imports. The base `schema` file must export a genuine table call (e.g.
116
+ `sqliteTable('users', { ... })`) matching `name`, since it determines which factory (`sqliteTable`/`pgTable`/etc.) and
117
+ SQL table name to use for the merged result.
118
+
119
+ #### Using a `factory`
120
+
121
+ Pass `factory` to `registerDatabaseTable` when you need shared defaults (e.g. timestamps) applied consistently across
122
+ dialects. Wrap the factory with `defineExtendableTable` so it receives a single `{ name, columns }` object instead of
123
+ a plain columns object — passing `name` through means the table's name only has to be maintained in one place, the
124
+ `registerDatabaseTable` call, instead of being duplicated as a string literal inside the factory itself. The factory
125
+ must spread `columns` *last*, after its own fields (including timestamps), so extended columns always end up last in
126
+ the generated migration:
127
+
128
+ ```ts
129
+ import type { ColumnBuilderBase } from 'drizzle-orm'
130
+ import { defineExtendableTable } from '@frigear-nu/module-kit/utils'
131
+
132
+ const defineUsersTable = defineExtendableTable(({ name, columns }: { name: string, columns: Record<string, ColumnBuilderBase> }) => {
133
+ return sqliteTable(name, {
29
134
  id: serial('id').primaryKey(),
30
135
  email: text('email').notNull().unique(),
31
136
  password: text('password').notNull(),
32
137
  createdAt: timestamp('created_at').defaultNow().notNull(),
33
138
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
34
- ...extras || {},
139
+ ...columns,
35
140
  })
36
- }
141
+ })
37
142
  ```
38
143
 
39
-
40
- The schema file exports the named factory, which accepts an object of additional columns and returns a table. The factory must spread the received `extra` columns *last* in the object passed to the table builder (after every one of its own fields, including timestamps), so extended columns always end up last in the generated migration, matching the order they were registered in. Other modules can register column files during setup:
41
-
42
144
  ```ts
43
- registerDatabaseTableExtension(nuxt, {
145
+ registerDatabaseTable(nuxt, {
44
146
  name: 'users',
147
+ factory: 'defineUsersTable',
45
148
  resolver: createResolver(import.meta.url),
46
- file: dialect => `./runtime/db/extra.${dialect}`,
149
+ schema: dialect => `./runtime/db/schema.${dialect}`,
150
+ override: options.settings?.schemas?.users,
47
151
  })
48
152
  ```
49
153
 
50
- Each extension file exports column objects named for their tables. For example, a Nuxt layer can extend several tables in `server/db/extend.sqlite.ts`:
154
+ #### `defineTableColumns`
155
+
156
+ A typed identity helper for defining a table's extension columns. Each extension file exports column objects named
157
+ for their tables. For example, a Nuxt layer can extend several tables in `server/db/extend.sqlite.ts`:
51
158
 
52
159
  ```ts
53
- export const users = defineExtendedColumns({ locale: text().default('da') })
54
- export const posts = defineExtendedColumns({ summary: text() })
160
+ import { defineTableColumns } from '@frigear-nu/module-kit/utils'
161
+
162
+ export const users = defineTableColumns({ locale: text().default('da') })
163
+ export const posts = defineTableColumns({ summary: text() })
55
164
  ```
56
165
 
57
- Only the export matching the table being built is used. Existing registered files are merged in registration order (later fields replace earlier fields); missing files for a dialect are skipped. Registered files with legacy default exports still work. Nuxt layer files are merged next, from base layers toward the host application. Older `server/db/<name>.extend.<dialect>` files with default exports are also loaded before each layer's shared file, so a shared export can override them. Pass `extensionFile: dialect => 'path/to/columns.' + dialect + '.ts'` to change the per-layer file location. If `override` is a path instead of `'default'`, the host schema replaces the generated table entirely; the path is resolved from the application's root directory.
166
+ Only the export matching the table being built is used. Existing registered files are merged in registration order
167
+ (later fields replace earlier fields); missing files for a dialect are skipped. Registered files with legacy default
168
+ exports still work. Nuxt layer files are merged next, from base layers toward the host application. Older
169
+ `server/db/<name>.extend.<dialect>` files with default exports are also loaded before each layer's shared file, so a
170
+ shared export can override them. Pass `extensionFile: dialect => 'path/to/columns.' + dialect + '.ts'` to change the
171
+ per-layer file location. If `override` is a path instead of `'default'`, the host schema replaces the generated table
172
+ entirely; the path is resolved from the application's root directory.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "0.0.3",
7
+ "version": "0.0.5",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "unknown"
@@ -0,0 +1,31 @@
1
+ export type ParsedTableExport = {
2
+ /** The callee + sql table name, present only when the export is a table factory call, e.g. `sqliteTable('users', {...})`. */
3
+ tableCall?: {
4
+ callee: string;
5
+ tableName: string;
6
+ extraArgs?: string[];
7
+ };
8
+ /** Column (or spread) entries keyed by property name, in source order. Values are the raw `key: value` source text. */
9
+ properties: Map<string, string>;
10
+ /** Named imports collected from the file, keyed by module specifier. */
11
+ imports: Map<string, Set<string>>;
12
+ };
13
+ /**
14
+ * Parses a "regular" drizzle schema file - i.e. a file that exports a table (or a plain columns object) without
15
+ * requiring any `defineExtendableTable`/`defineTableColumns` wrapper - and extracts the column properties and
16
+ * imports belonging to the export matching `exportName`. Supports:
17
+ *
18
+ * - `export const users = sqliteTable('users', { ... })` (or `pgTable`/`mysqlTable`, any 2+ arg factory call)
19
+ * - `export const users = { ... }` (a plain columns object)
20
+ * - `export const users = defineTableColumns({ ... })` (a single-argument wrapper call)
21
+ * - `export default { ... }` / `export default sqliteTable(...)` / `export default defineTableColumns({ ... })`
22
+ * when `exportName` is `'default'`
23
+ */
24
+ export declare const parseRegularTableExport: (filePath: string, exportName: string) => ParsedTableExport | undefined;
25
+ /**
26
+ * Lists the names of every named export in a "regular" drizzle schema file that looks like a table (or columns)
27
+ * export - i.e. every export that `parseRegularTableExport` would be able to parse. Used by `registerDatabaseTable`
28
+ * to auto-discover table names when none are explicitly provided. Does not include the `default` export, since it
29
+ * has no name to register a table under.
30
+ */
31
+ export declare const listRegularTableExportNames: (filePath: string) => string[];
@@ -0,0 +1,76 @@
1
+ import { readFileSync } from "node:fs";
2
+ import ts from "typescript";
3
+ const parseSource = (filePath) => {
4
+ const text = readFileSync(filePath, "utf8");
5
+ return ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
6
+ };
7
+ const collectImports = (source) => {
8
+ const imports = /* @__PURE__ */ new Map();
9
+ for (const statement of source.statements) {
10
+ if (ts.isImportDeclaration(statement) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && ts.isStringLiteralLike(statement.moduleSpecifier)) {
11
+ const specifiers = imports.get(statement.moduleSpecifier.text) ?? /* @__PURE__ */ new Set();
12
+ for (const element of statement.importClause.namedBindings.elements) specifiers.add(element.getText());
13
+ imports.set(statement.moduleSpecifier.text, specifiers);
14
+ }
15
+ }
16
+ return imports;
17
+ };
18
+ const toTableResult = (initializer, imports) => {
19
+ let objectLiteral;
20
+ let tableCall;
21
+ if (ts.isCallExpression(initializer)) {
22
+ const args = initializer.arguments;
23
+ if (args.length >= 2 && ts.isStringLiteralLike(args[0]) && ts.isObjectLiteralExpression(args[1])) {
24
+ const extraArgs = args.length > 2 ? args.slice(2).map((arg) => arg.getText()) : void 0;
25
+ tableCall = { callee: initializer.expression.getText(), tableName: args[0].text, extraArgs };
26
+ objectLiteral = args[1];
27
+ } else if (args.length === 1 && ts.isObjectLiteralExpression(args[0])) {
28
+ objectLiteral = args[0];
29
+ }
30
+ } else if (ts.isObjectLiteralExpression(initializer)) {
31
+ objectLiteral = initializer;
32
+ }
33
+ if (!objectLiteral) return void 0;
34
+ const properties = /* @__PURE__ */ new Map();
35
+ for (const property of objectLiteral.properties) {
36
+ if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property)) {
37
+ properties.set(property.name.getText(), property.getText());
38
+ } else if (ts.isSpreadAssignment(property)) {
39
+ properties.set(`__spread_${properties.size}`, property.getText());
40
+ }
41
+ }
42
+ return { tableCall, properties, imports };
43
+ };
44
+ export const parseRegularTableExport = (filePath, exportName) => {
45
+ const source = parseSource(filePath);
46
+ const imports = collectImports(source);
47
+ for (const statement of source.statements) {
48
+ if (ts.isVariableStatement(statement) && statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
49
+ for (const declaration of statement.declarationList.declarations) {
50
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === exportName && declaration.initializer) {
51
+ const result = toTableResult(declaration.initializer, imports);
52
+ if (result) return result;
53
+ }
54
+ }
55
+ } else if (exportName === "default" && ts.isExportAssignment(statement) && !statement.isExportEquals) {
56
+ const result = toTableResult(statement.expression, imports);
57
+ if (result) return result;
58
+ }
59
+ }
60
+ return void 0;
61
+ };
62
+ export const listRegularTableExportNames = (filePath) => {
63
+ const source = parseSource(filePath);
64
+ const imports = collectImports(source);
65
+ const names = [];
66
+ for (const statement of source.statements) {
67
+ if (ts.isVariableStatement(statement) && statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
68
+ for (const declaration of statement.declarationList.declarations) {
69
+ if (ts.isIdentifier(declaration.name) && declaration.initializer && toTableResult(declaration.initializer, imports)) {
70
+ names.push(declaration.name.text);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ return names;
76
+ };
@@ -16,11 +16,50 @@ declare module '@nuxt/schema' {
16
16
  'module-kit:db:table:extend': TableExtenderFn;
17
17
  }
18
18
  }
19
- export declare const defineExtendedColumns: <T extends Record<string, ColumnBuilderBase>>(columns: T) => T;
20
- export declare const extendDatabaseSchema: (nuxt: Nuxt, extender: DbExtenderFn) => Promise<() => void>;
21
- type DatabaseTableOptions = {
19
+ /**
20
+ * A typed identity helper for defining a table's extension columns. It exists purely for type
21
+ * inference/clarity when authoring `server/db/extend.<dialect>.ts` files — it returns the columns
22
+ * object unchanged.
23
+ */
24
+ export declare const defineTableColumns: <T extends Record<string, ColumnBuilderBase>>(columns: T) => T;
25
+ type TableDefinerProps = {
22
26
  name: string;
23
- factory: string;
27
+ columns: Record<string, ColumnBuilderBase>;
28
+ };
29
+ type TableDefinerFn<T> = (props: TableDefinerProps) => T;
30
+ /**
31
+ * Wraps a Drizzle table factory so it receives `{ name, columns }`, letting the factory pass
32
+ * `name` straight through to e.g. `sqliteTable(name, columns)` instead of hard-coding it, so the
33
+ * table name only has to be maintained once, in the `registerDatabaseTable` call.
34
+ */
35
+ export declare const defineExtendableTable: <T>(table: TableDefinerFn<T>) => (props: TableDefinerProps) => T;
36
+ /**
37
+ * Lower-level primitive behind `registerDatabaseTable`. Registers a callback on the
38
+ * `hub:db:schema:extend` hook, which receives the current `dialect` and the `paths` array of
39
+ * schema files to include in the generated database schema. Most modules should use
40
+ * `registerDatabaseTable` instead, unless they need full control over how their schema file is
41
+ * built.
42
+ */
43
+ export declare const onDatabaseSchemaExtend: (nuxt: Nuxt, extender: DbExtenderFn) => Promise<() => void>;
44
+ type DatabaseTableOptions = {
45
+ /**
46
+ * The name of the table's export (e.g. `users`). Pass an array of names (e.g. `['users', 'userPasskeys']`) to
47
+ * register several tables that are exported from the same `schema` file in one go - they're generated into a
48
+ * single file so that tables can reference each other (e.g. a foreign key `.references(() => users.id)`). An
49
+ * array of names (or omitting `name` entirely) is only supported when `factory` is omitted.
50
+ *
51
+ * When omitted, every table-like export in `schema` (and its extension files) is registered automatically - this
52
+ * is equivalent to passing an array of every such export's name.
53
+ */
54
+ name?: string | string[];
55
+ /**
56
+ * The exported factory function name to call with merged extension columns (e.g. `defineUsersTable`), as produced
57
+ * by `defineExtendableTable`. When omitted, `schema` (and every registered/layer extension file) is treated as a
58
+ * "regular" drizzle file - e.g. `export const users = sqliteTable('users', { ... })` - and its columns are merged
59
+ * directly, without requiring any helper wrapper. Only supported when `name` is a single string, since the
60
+ * factory is called once with a single table's columns.
61
+ */
62
+ factory?: string;
24
63
  resolver: ReturnType<typeof createResolver>;
25
64
  schema: (dialect: string) => string;
26
65
  override?: string;
@@ -31,6 +70,21 @@ type TableExtensionOptions = {
31
70
  resolver: ReturnType<typeof createResolver>;
32
71
  file: (dialect: string) => string;
33
72
  };
34
- export declare const registerDatabaseTableExtension: (nuxt: Nuxt, options: TableExtensionOptions) => void;
35
- export declare const extendDatabaseTable: (nuxt: Nuxt, options: DatabaseTableOptions) => Promise<() => void>;
73
+ /**
74
+ * Registers a column file to be merged into a named table whenever its schema is generated (see
75
+ * `registerDatabaseTable`). Fires the `module-kit:db:table:extend` hook internally, which
76
+ * `registerDatabaseTable` listens to.
77
+ */
78
+ export declare const extendDatabaseTable: (nuxt: Nuxt, options: TableExtensionOptions) => void;
79
+ /**
80
+ * The table-owning module registers its schema with `registerDatabaseTable`. It generates the
81
+ * table by importing the named `factory` from `schema`, merging in any columns registered
82
+ * through `extendDatabaseTable` and any Nuxt-layer extension files, then calling the factory with
83
+ * `{ name, columns }`.
84
+ *
85
+ * When `name` is an array, or omitted entirely, every (named or auto-discovered) table is generated into a single
86
+ * file - this lets tables declared in the same `schema` file reference each other directly (e.g. a foreign key
87
+ * `.references(() => users.id)` from a `userPasskeys` table), since they share the same module scope.
88
+ */
89
+ export declare const registerDatabaseTable: (nuxt: Nuxt, options: DatabaseTableOptions) => Promise<() => void>;
36
90
  export {};
@@ -1,12 +1,18 @@
1
1
  import { addTemplate, createResolver } from "@nuxt/kit";
2
2
  import { existsSync } from "node:fs";
3
- export const defineExtendedColumns = (columns) => {
3
+ import { listRegularTableExportNames, parseRegularTableExport } from "./regular-table.js";
4
+ export const defineTableColumns = (columns) => {
4
5
  return columns;
5
6
  };
6
- export const extendDatabaseSchema = async (nuxt, extender) => {
7
+ export const defineExtendableTable = (table) => {
8
+ return (props) => {
9
+ return table(props);
10
+ };
11
+ };
12
+ export const onDatabaseSchemaExtend = async (nuxt, extender) => {
7
13
  return nuxt.hook("hub:db:schema:extend", extender);
8
14
  };
9
- export const registerDatabaseTableExtension = (nuxt, options) => {
15
+ export const extendDatabaseTable = (nuxt, options) => {
10
16
  nuxt.hook("module-kit:db:table:extend", async ({ name, dialect, paths }) => {
11
17
  if (name === options.name) {
12
18
  const file = await options.resolver.resolvePath(options.file(dialect));
@@ -14,35 +20,104 @@ export const registerDatabaseTableExtension = (nuxt, options) => {
14
20
  }
15
21
  });
16
22
  };
17
- export const extendDatabaseTable = (nuxt, options) => {
23
+ export const registerDatabaseTable = (nuxt, options) => {
18
24
  const { name, factory, resolver, schema, override, extensionFile } = options;
19
- return extendDatabaseSchema(nuxt, async ({ dialect, paths }) => {
25
+ if (factory && (name === void 0 || Array.isArray(name))) {
26
+ throw new Error(
27
+ '[module-kit] registerDatabaseTable requires a single "name" (not an array, and not omitted) when "factory" is set. Omit "factory" to register multiple (or auto-discovered) tables from the same regular drizzle schema file.'
28
+ );
29
+ }
30
+ return onDatabaseSchemaExtend(nuxt, async ({ dialect, paths }) => {
20
31
  if (override && override !== "default") {
21
32
  paths.push(await resolver.resolvePath(override, { cwd: nuxt.options.rootDir }));
22
33
  return;
23
34
  }
24
- const registered = [];
25
- await nuxt.callHook("module-kit:db:table:extend", { name, dialect, paths: registered });
26
- const extensions = registered.map((file) => ({ file, legacy: true }));
35
+ const schemaPath = await resolver.resolvePath(schema(dialect));
36
+ const names = name === void 0 ? listRegularTableExportNames(schemaPath) : Array.isArray(name) ? name : [name];
37
+ if (names.length === 0) {
38
+ throw new Error(
39
+ `[module-kit] Could not find any table exports in "${schemaPath}" to register. "schema" must be a regular drizzle file, e.g. \`export const users = sqliteTable('users', { ... })\`.`
40
+ );
41
+ }
42
+ const extensionsByName = /* @__PURE__ */ new Map();
43
+ for (const tableName2 of names) {
44
+ const registered = [];
45
+ await nuxt.callHook("module-kit:db:table:extend", { name: tableName2, dialect, paths: registered });
46
+ extensionsByName.set(tableName2, registered.map((file) => ({ file, legacy: true })));
47
+ }
27
48
  const layerRoots = [...new Set(nuxt.options._layers?.map((layer) => layer.config.rootDir).reverse() ?? [])];
28
49
  for (const root of [...layerRoots.filter((root2) => root2 !== nuxt.options.rootDir), nuxt.options.rootDir]) {
29
50
  const rootResolver = createResolver(root);
30
51
  const extensionPath = await rootResolver.resolvePath(extensionFile?.(dialect) ?? `server/db/extend.${dialect}.ts`);
31
52
  if (!extensionFile) {
32
- const legacyPath = await rootResolver.resolvePath(`server/db/${name}.extend.${dialect}`);
33
- if (existsSync(legacyPath)) extensions.push({ file: legacyPath, legacy: true });
53
+ for (const tableName2 of names) {
54
+ const legacyPath = await rootResolver.resolvePath(`server/db/${tableName2}.extend.${dialect}`);
55
+ if (existsSync(legacyPath)) extensionsByName.get(tableName2).push({ file: legacyPath, legacy: true });
56
+ }
57
+ }
58
+ if (existsSync(extensionPath)) {
59
+ for (const tableName2 of names) extensionsByName.get(tableName2).push({ file: extensionPath, legacy: false });
34
60
  }
35
- if (existsSync(extensionPath)) extensions.push({ file: extensionPath, legacy: false });
36
61
  }
62
+ if (!factory) {
63
+ const template2 = buildRegularTableTemplate({ names, dialect, schemaPath, extensionsByName });
64
+ paths.push(template2.dst);
65
+ return;
66
+ }
67
+ const tableName = names[0];
68
+ const extensions = extensionsByName.get(tableName);
37
69
  const template = addTemplate({
38
- filename: `${name}/schema.${dialect}.ts`,
70
+ filename: `${tableName}/schema.${dialect}.ts`,
39
71
  write: true,
40
72
  getContents: () => `
41
73
  import { ${factory} } from ${JSON.stringify(resolver.resolve(schema(dialect)))}
42
74
  ${extensions.map(({ file }, index) => `import * as extra${index} from ${JSON.stringify(file)}`).join("\n")}
43
- export const ${name} = ${factory}({ ${extensions.map(({ legacy }, index) => legacy ? `...(extra${index}[${JSON.stringify(name)} as keyof typeof extra${index}] ?? Object.entries(extra${index}).find(([key]) => key === 'default')?.[1] ?? {})` : `...(extra${index}[${JSON.stringify(name)} as keyof typeof extra${index}] ?? {})`).join(", ")} })
75
+ export const ${tableName} = ${factory}({ name: ${JSON.stringify(tableName)}, columns: { ${extensions.map(({ legacy }, index) => legacy ? `...(extra${index}[${JSON.stringify(tableName)} as keyof typeof extra${index}] ?? Object.entries(extra${index}).find(([key]) => key === 'default')?.[1] ?? {})` : `...(extra${index}[${JSON.stringify(tableName)} as keyof typeof extra${index}] ?? {})`).join(", ")} } })
44
76
  `
45
77
  });
46
78
  paths.push(template.dst);
47
79
  });
48
80
  };
81
+ const buildRegularTableTemplate = (options) => {
82
+ const { names, dialect, schemaPath, extensionsByName } = options;
83
+ const importsByModule = /* @__PURE__ */ new Map();
84
+ const tables = names.map((name) => {
85
+ const base = parseRegularTableExport(schemaPath, name);
86
+ if (!base?.tableCall) {
87
+ throw new Error(
88
+ `[module-kit] Could not find a table export named "${name}" in "${schemaPath}". When "factory" is omitted, "schema" must be a regular drizzle file, e.g. \`export const ${name} = sqliteTable(${JSON.stringify(name)}, { ... })\`.`
89
+ );
90
+ }
91
+ const properties = new Map(base.properties);
92
+ for (const [module, specifiers] of base.imports) {
93
+ const set = importsByModule.get(module) ?? /* @__PURE__ */ new Set();
94
+ for (const specifier of specifiers) set.add(specifier);
95
+ importsByModule.set(module, set);
96
+ }
97
+ for (const { file } of extensionsByName.get(name) ?? []) {
98
+ const parsed = parseRegularTableExport(file, name);
99
+ if (!parsed) continue;
100
+ for (const [key, value] of parsed.properties) {
101
+ properties.delete(key);
102
+ properties.set(key, value);
103
+ }
104
+ for (const [module, specifiers] of parsed.imports) {
105
+ const set = importsByModule.get(module) ?? /* @__PURE__ */ new Set();
106
+ for (const specifier of specifiers) set.add(specifier);
107
+ importsByModule.set(module, set);
108
+ }
109
+ }
110
+ return { name, tableCall: base.tableCall, properties };
111
+ });
112
+ return addTemplate({
113
+ filename: `${names.join("+")}/schema.${dialect}.ts`,
114
+ write: true,
115
+ getContents: () => `
116
+ ${[...importsByModule].map(([module, specifiers]) => `import { ${[...specifiers].join(", ")} } from ${JSON.stringify(module)}`).join("\n")}
117
+ ${tables.map(({ name, tableCall, properties }) => `
118
+ export const ${name} = ${tableCall.callee}(${JSON.stringify(tableCall.tableName)}, {
119
+ ${[...properties.values()].map((property) => ` ${property},`).join("\n")}
120
+ }${tableCall.extraArgs?.length ? `, ${tableCall.extraArgs.join(", ")}` : ""})
121
+ `).join("")}`
122
+ });
123
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frigear-nu/module-kit",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Frigear Module Kit",
@@ -38,8 +38,10 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "@nuxt/kit": "^4.5.2",
41
+ "@nuxthub/core": "^0.10.8",
41
42
  "defu": "^6.1.7",
42
43
  "scule": "^1.3.0",
44
+ "typescript": "^6.0.3",
43
45
  "ufo": "^1.6.4",
44
46
  "zod": "^4.6.5"
45
47
  },
@@ -53,7 +55,6 @@
53
55
  "changelogen": "^0.6.2",
54
56
  "eslint": "^10.10.0",
55
57
  "nuxt": "^4.5.2",
56
- "typescript": "^6.0.3",
57
58
  "vitest": "^5.0.1",
58
59
  "vue-tsc": "^3.3.11"
59
60
  },