@pattern-stack/codegen 0.17.1 → 0.18.0
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/CHANGELOG.md +36 -0
- package/README.md +106 -2
- package/dist/{chunk-SFQRETXJ.js → chunk-2VGVSL2D.js} +6 -6
- package/dist/{chunk-VNBC3VXM.js → chunk-3A34R6CI.js} +7 -7
- package/dist/{chunk-FVNAU7VO.js → chunk-7MMS36AN.js} +6 -6
- package/dist/{chunk-FWRL7BZ5.js → chunk-C5E7H553.js} +25 -15
- package/dist/chunk-C5E7H553.js.map +1 -0
- package/dist/{chunk-IOQMMH6C.js → chunk-CFFTPWHM.js} +79 -4
- package/dist/chunk-CFFTPWHM.js.map +1 -0
- package/dist/{chunk-HOIRY5XP.js → chunk-EWYI5GGJ.js} +10 -10
- package/dist/{chunk-BHZP6LOV.js → chunk-IN3EWFB4.js} +4 -4
- package/dist/{chunk-CZQUOIDY.js → chunk-J7JMVS2B.js} +4 -4
- package/dist/{chunk-KSTZIULO.js → chunk-K2I6XIK5.js} +4 -4
- package/dist/{chunk-T6SCOJF4.js → chunk-NXHL5YII.js} +4 -4
- package/dist/{chunk-JA7GJDNI.js → chunk-PKDS6QIJ.js} +4 -4
- package/dist/{chunk-MYQIQ27N.js → chunk-Q6LRJ4VI.js} +51 -2
- package/dist/chunk-Q6LRJ4VI.js.map +1 -0
- package/dist/{chunk-EJBK7I4F.js → chunk-R4BPUUB5.js} +3 -3
- package/dist/{chunk-4PFF3ED4.js → chunk-RKNW56RU.js} +5 -5
- package/dist/{chunk-SGSWVNNB.js → chunk-TBGTMALE.js} +4 -4
- package/dist/{chunk-GM3RMJIJ.js → chunk-VHAR2BGH.js} +4 -4
- package/dist/{chunk-DUMI2J5M.js → chunk-VQOAATIG.js} +4 -4
- package/dist/{chunk-HPS554L4.js → chunk-X6BP6LI5.js} +6 -6
- package/dist/{chunk-PSDVGPQR.js → chunk-YZLBU6O2.js} +9 -9
- package/dist/runtime/shared/openapi/index.js +3 -3
- package/dist/runtime/subsystems/analytics/analytics.module.js +2 -2
- package/dist/runtime/subsystems/analytics/index.js +4 -4
- package/dist/runtime/subsystems/auth/auth.module.js +3 -3
- package/dist/runtime/subsystems/auth/index.js +10 -10
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +2 -2
- package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +2 -2
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +6 -6
- package/dist/runtime/subsystems/bridge/bridge.module.js +17 -17
- package/dist/runtime/subsystems/bridge/index.js +24 -24
- package/dist/runtime/subsystems/cache/cache.module.js +1 -1
- package/dist/runtime/subsystems/cache/index.js +3 -3
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +3 -3
- package/dist/runtime/subsystems/events/events.module.js +6 -6
- package/dist/runtime/subsystems/events/generated/bus.js +2 -2
- package/dist/runtime/subsystems/events/generated/index.js +2 -2
- package/dist/runtime/subsystems/events/index.js +10 -10
- package/dist/runtime/subsystems/index.js +64 -64
- package/dist/runtime/subsystems/integration/index.js +10 -10
- package/dist/runtime/subsystems/integration/integration.module.js +2 -2
- package/dist/runtime/subsystems/jobs/index.js +21 -21
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +5 -5
- package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +3 -3
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +2 -2
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +3 -3
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +2 -2
- package/dist/runtime/subsystems/jobs/job-worker.d.ts +8 -0
- package/dist/runtime/subsystems/jobs/job-worker.js +3 -3
- package/dist/runtime/subsystems/jobs/job-worker.module.js +11 -11
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +9 -9
- package/dist/runtime/subsystems/jobs/pg-notify.d.ts +25 -1
- package/dist/runtime/subsystems/jobs/pg-notify.js +1 -1
- package/dist/src/cli/index.js +1408 -245
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +5 -5
- package/package.json +1 -1
- package/runtime/subsystems/jobs/job-worker.ts +29 -11
- package/runtime/subsystems/jobs/pg-notify.ts +63 -3
- package/src/config/locations.mjs +0 -6
- package/src/config/paths.mjs +0 -13
- package/templates/entity/new/prompt.js +12 -88
- package/dist/chunk-FWRL7BZ5.js.map +0 -1
- package/dist/chunk-IOQMMH6C.js.map +0 -1
- package/dist/chunk-MYQIQ27N.js.map +0 -1
- package/templates/entity/new/frontend/_inject-entities-entry.ejs.t +0 -7
- package/templates/entity/new/frontend/_inject-entities-import.ejs.t +0 -7
- package/templates/entity/new/frontend/collections/_ensure-anchor-collections.ejs.t +0 -10
- package/templates/entity/new/frontend/collections/_inject-index.ejs.t +0 -9
- package/templates/entity/new/frontend/collections/_inject-schema-import.ejs.t +0 -9
- package/templates/entity/new/frontend/collections/collection.ejs.t +0 -86
- package/templates/entity/new/frontend/collections/collections-base.ejs.t +0 -35
- package/templates/entity/new/frontend/entity/collection.ejs.t +0 -173
- package/templates/entity/new/frontend/entity/combined.ejs.t +0 -505
- package/templates/entity/new/frontend/entity/fields.ejs.t +0 -105
- package/templates/entity/new/frontend/entity/hooks.ejs.t +0 -74
- package/templates/entity/new/frontend/entity/index.ejs.t +0 -22
- package/templates/entity/new/frontend/entity/mutation-hooks.ejs.t +0 -85
- package/templates/entity/new/frontend/entity/mutations.ejs.t +0 -39
- package/templates/entity/new/frontend/entity/types.ejs.t +0 -60
- package/templates/entity/new/frontend/generated/_inject-index-export.ejs.t +0 -7
- package/templates/entity/new/frontend/generated/_inject-index-import.ejs.t +0 -7
- package/templates/entity/new/frontend/generated/_inject-index-registry.ejs.t +0 -7
- package/templates/entity/new/frontend/store/_inject-collection-import.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-collections.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-entity.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-import.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-lookups.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-resolve.ejs.t +0 -10
- package/templates/entity/new/frontend/store/hooks.ejs.t +0 -73
- package/templates/entity/new/frontend/unified-entity.ejs.t +0 -29
- /package/dist/{chunk-SFQRETXJ.js.map → chunk-2VGVSL2D.js.map} +0 -0
- /package/dist/{chunk-VNBC3VXM.js.map → chunk-3A34R6CI.js.map} +0 -0
- /package/dist/{chunk-FVNAU7VO.js.map → chunk-7MMS36AN.js.map} +0 -0
- /package/dist/{chunk-HOIRY5XP.js.map → chunk-EWYI5GGJ.js.map} +0 -0
- /package/dist/{chunk-BHZP6LOV.js.map → chunk-IN3EWFB4.js.map} +0 -0
- /package/dist/{chunk-CZQUOIDY.js.map → chunk-J7JMVS2B.js.map} +0 -0
- /package/dist/{chunk-KSTZIULO.js.map → chunk-K2I6XIK5.js.map} +0 -0
- /package/dist/{chunk-T6SCOJF4.js.map → chunk-NXHL5YII.js.map} +0 -0
- /package/dist/{chunk-JA7GJDNI.js.map → chunk-PKDS6QIJ.js.map} +0 -0
- /package/dist/{chunk-EJBK7I4F.js.map → chunk-R4BPUUB5.js.map} +0 -0
- /package/dist/{chunk-4PFF3ED4.js.map → chunk-RKNW56RU.js.map} +0 -0
- /package/dist/{chunk-SGSWVNNB.js.map → chunk-TBGTMALE.js.map} +0 -0
- /package/dist/{chunk-GM3RMJIJ.js.map → chunk-VHAR2BGH.js.map} +0 -0
- /package/dist/{chunk-DUMI2J5M.js.map → chunk-VQOAATIG.js.map} +0 -0
- /package/dist/{chunk-HPS554L4.js.map → chunk-X6BP6LI5.js.map} +0 -0
- /package/dist/{chunk-PSDVGPQR.js.map → chunk-YZLBU6O2.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../runtime/subsystems/jobs/pg-notify.ts"],"sourcesContent":["/**\n * PgNotifyListener + pgNotify — Postgres LISTEN/NOTIFY wakeups\n * (LISTEN-NOTIFY-1, dogfood gap #7).\n *\n * The drizzle jobs worker and events outbox drainer poll on an interval today\n * (default 1 s/hop). With `listen_notify` enabled, a row write that makes work\n * claimable emits an in-transaction `pg_notify(...)`; a dedicated listener\n * connection wakes the polling loop the moment the writing transaction commits.\n *\n * Two halves:\n * - `pgNotify(tx, channel, payload)` — fire an in-tx `pg_notify`. MUST be\n * called with the SAME transaction handle as the row write it announces, so\n * Postgres delivers it only on commit (the transactional-outbox guarantee).\n * - `PgNotifyListener` — owns a single long-lived `pg.PoolClient`, issues\n * `LISTEN <channel>`, forwards each notification's payload to an owner\n * callback, debounces bursts, and reconnects with capped backoff on drop.\n *\n * **Polling never stops.** This is a wake-early optimisation layered ON TOP of\n * interval polling. A lost notification (listener down, pooler eats the LISTEN,\n * etc.) degrades to today's poll latency, never to lost work — the claim/drain\n * query remains the source of truth.\n *\n * **PgBouncer caveat:** session-scoped `LISTEN` does not survive a\n * transaction-mode pooler. `listen_notify` requires a direct (or session-mode)\n * connection; behind a transaction pooler notifies are simply never received and\n * the system degrades to polling. See the jobs config block / skill.\n */\n// TODO(logging-subsystem): swap to ILogger once ADR-028 lands\nimport { Logger } from '@nestjs/common';\nimport { sql } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport type { DrizzleTransaction } from '../events/event-bus.protocol';\n\n/** Channel the jobs worker LISTENs on; payload = pool name. */\nexport const JOBS_WAKE_CHANNEL = 'codegen_jobs_wake';\n/** Channel the events drainer LISTENs on; payload = event pool (or ''). */\nexport const EVENTS_WAKE_CHANNEL = 'codegen_events_wake';\n\n/**\n * Emit an in-transaction `pg_notify`. Call with the SAME `tx`/client handle as\n * the row write being announced so delivery is gated on commit. `payload` is a\n * short plain string (a pool name); it is NOT JSON — the wake is a hint and the\n * subsequent claim/drain query is authoritative. Channel names are framework\n * constants (never user input), so the `set_config`-free literal-channel form is\n * safe; the payload is bound as a parameter.\n */\nexport async function pgNotify(\n tx: DrizzleClient | DrizzleTransaction,\n channel: string,\n payload: string,\n): Promise<void> {\n const client = tx as DrizzleClient;\n // `pg_notify(channel, payload)` is the function form (vs the `NOTIFY chan,\n // 'payload'` statement form) precisely because it accepts bound parameters —\n // the payload is parameterised, never string-concatenated.\n await client.execute(sql`select pg_notify(${channel}, ${payload})`);\n}\n\n/** Minimal structural view of the `pg` Client/PoolClient surface we touch. */\ninterface PgListenClient {\n query(text: string): Promise<unknown>;\n on(event: 'notification', cb: (msg: { channel: string; payload?: string }) => void): void;\n on(event: 'error', cb: (err: Error) => void): void;\n removeAllListeners?: (event?: string) => void;\n release?: (err?: boolean) => void;\n end?: () => Promise<void>;\n}\n\n/** Minimal structural view of the `pg` Pool's `connect()`. */\ninterface PgPoolish {\n connect(): Promise<PgListenClient>;\n}\n\nconst DEFAULT_BACKOFF_MIN_MS = 100;\nconst DEFAULT_BACKOFF_MAX_MS = 5_000;\n\nexport interface PgNotifyListenerOptions {\n /** Channel to LISTEN on. */\n channel: string;\n /**\n * The underlying `pg.Pool` — obtained from `drizzleClient.$client`. A\n * dedicated `PoolClient` is checked out and held for the listener's lifetime\n * (separate from the query pool so a slow query never delays a wake).\n */\n pool: PgPoolish;\n /**\n * Called for every notification on `channel`, with the raw payload string\n * (`''` when Postgres delivers an empty payload). The owner decides whether\n * the payload is relevant (e.g. \"is this one of my pools?\") and debounces its\n * own claim cycle.\n */\n onNotify: (payload: string) => void;\n /** Label used in log lines (e.g. 'jobs:interactive', 'events'). */\n label: string;\n backoffMinMs?: number;\n backoffMaxMs?: number;\n}\n\n/**\n * Holds a dedicated listener connection and forwards notifications to `onNotify`.\n * Reconnects with capped exponential backoff on drop; logs the first failure +\n * the recovery exactly once each so a flapping connection doesn't flood logs.\n */\nexport class PgNotifyListener {\n private readonly logger: Logger;\n private client: PgListenClient | null = null;\n private stopped = false;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private backoffMs: number;\n private readonly backoffMinMs: number;\n private readonly backoffMaxMs: number;\n /** WARN-once gate so a flapping listener doesn't spam the log. */\n private warnedDown = false;\n\n constructor(private readonly opts: PgNotifyListenerOptions) {\n this.logger = new Logger(`PgNotifyListener(${opts.label})`);\n this.backoffMinMs = opts.backoffMinMs ?? DEFAULT_BACKOFF_MIN_MS;\n this.backoffMaxMs = opts.backoffMaxMs ?? DEFAULT_BACKOFF_MAX_MS;\n this.backoffMs = this.backoffMinMs;\n }\n\n /** Begin listening. Idempotent-ish: a second call while connected is a no-op. */\n async start(): Promise<void> {\n this.stopped = false;\n await this.connect();\n }\n\n /** Stop listening + release the connection. Safe to call repeatedly. */\n async stop(): Promise<void> {\n this.stopped = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n await this.releaseClient();\n }\n\n private async connect(): Promise<void> {\n if (this.stopped) return;\n try {\n const client = await this.opts.pool.connect();\n client.on('notification', (msg) => {\n if (msg.channel !== this.opts.channel) return;\n try {\n this.opts.onNotify(msg.payload ?? '');\n } catch (err) {\n this.logger.error(`onNotify threw: ${(err as Error).message}`);\n }\n });\n client.on('error', (err) => {\n // A connection-level error is the signal to reconnect. Don't double-log\n // here — scheduleReconnect owns the WARN-once.\n this.logger.debug?.(`listener connection error: ${err.message}`);\n this.handleDrop();\n });\n await client.query(`LISTEN ${this.opts.channel}`);\n this.client = client;\n // Recovery: only announce if we had previously warned about being down.\n if (this.warnedDown) {\n this.logger.log(\n `listener reconnected; LISTEN ${this.opts.channel} re-established`,\n );\n this.warnedDown = false;\n }\n this.backoffMs = this.backoffMinMs;\n } catch (err) {\n this.handleConnectFailure(err);\n }\n }\n\n /** Connection dropped after being established → reconnect. */\n private handleDrop(): void {\n if (this.stopped) return;\n void this.releaseClient().finally(() => this.scheduleReconnect());\n }\n\n /** Initial / reconnect `connect()` threw. */\n private handleConnectFailure(err: unknown): void {\n this.scheduleReconnect(err);\n }\n\n private scheduleReconnect(err?: unknown): void {\n if (this.stopped) return;\n if (!this.warnedDown) {\n this.warnedDown = true;\n this.logger.warn(\n `listener down — falling back to interval polling until reconnect. ` +\n `Cause: ${err instanceof Error ? err.message : 'connection lost'}. ` +\n `(This degrades latency, not durability — polling still drives all work.)`,\n );\n }\n if (this.reconnectTimer) clearTimeout(this.reconnectTimer);\n const delay = this.backoffMs;\n this.backoffMs = Math.min(this.backoffMs * 2, this.backoffMaxMs);\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n void this.connect();\n }, delay);\n }\n\n private async releaseClient(): Promise<void> {\n const client = this.client;\n this.client = null;\n if (!client) return;\n try {\n client.removeAllListeners?.('notification');\n client.removeAllListeners?.('error');\n // A listener client is a checked-out pool connection; release it back\n // with `release(true)` (destroy) so a half-broken socket isn't reused.\n if (client.release) client.release(true);\n else if (client.end) await client.end();\n } catch {\n // best-effort teardown\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: "<%= frontendEnabled ? (generate.collections ? `${locations.frontendCollections.path}/collections.ts` : '') : '' %>"
|
|
3
|
-
inject: true
|
|
4
|
-
append: true
|
|
5
|
-
skip_if: "// Codegen collections"
|
|
6
|
-
---
|
|
7
|
-
<% if (generate.collections) { -%>
|
|
8
|
-
|
|
9
|
-
// Codegen collections
|
|
10
|
-
<% } -%>
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: "<%= frontendEnabled ? (generate.collectionsIndex ? `${locations.frontendCollections.path}/index.ts` : '') : '' %>"
|
|
3
|
-
inject: true
|
|
4
|
-
after: "// Generated entity collections"
|
|
5
|
-
skip_if: "from './collections'"
|
|
6
|
-
---
|
|
7
|
-
<% if (generate.collectionsIndex) { -%>
|
|
8
|
-
export * from './collections';
|
|
9
|
-
<% } -%>
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: "<%= frontendEnabled ? (generate.collections && !frontend.collections?.schemaPrefix ? `${locations.frontendCollections.path}/collections.ts` : '') : '' %>"
|
|
3
|
-
inject: true
|
|
4
|
-
after: "// Codegen schema imports"
|
|
5
|
-
skip_if: <%= camelName %>Schema
|
|
6
|
-
---
|
|
7
|
-
<% if (generate.collections && !frontend.collections?.schemaPrefix) { -%>
|
|
8
|
-
<%= camelName %>Schema,
|
|
9
|
-
<% } -%>
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: "<%= frontendEnabled ? (generate.collections ? `${locations.frontendCollections.path}/collections.ts` : '') : '' %>"
|
|
3
|
-
inject: true
|
|
4
|
-
after: "// Codegen collections"
|
|
5
|
-
skip_if: <%= camelName %>Collection
|
|
6
|
-
---
|
|
7
|
-
<% if (generate.collections) { -%>
|
|
8
|
-
<%
|
|
9
|
-
// Determine the URL expression based on config
|
|
10
|
-
const hasApiBaseUrl = !!frontend.sync.apiBaseUrlImport;
|
|
11
|
-
// For the URL path, use API_BASE_URL if configured
|
|
12
|
-
const shapeUrlExpr = hasApiBaseUrl
|
|
13
|
-
? '`${API_BASE_URL}/' + plural + '`'
|
|
14
|
-
: '`' + frontend.sync.shapeUrl + '/' + plural + '`';
|
|
15
|
-
// REST list endpoint for 'api' sync mode
|
|
16
|
-
const apiUrlExpr = hasApiBaseUrl
|
|
17
|
-
? '`${API_BASE_URL}/' + plural + '`'
|
|
18
|
-
: '`' + frontend.sync.apiUrl + '/' + plural + '`';
|
|
19
|
-
const schemaPrefix = frontend.collections?.schemaPrefix ?? 'schema.';
|
|
20
|
-
-%>
|
|
21
|
-
<% if (frontend.sync.mode === 'api') { -%>
|
|
22
|
-
export const <%= camelName %>Collection = createCollection(
|
|
23
|
-
queryCollectionOptions({
|
|
24
|
-
id: '<%= plural %>',
|
|
25
|
-
queryKey: ['<%= plural %>'],
|
|
26
|
-
queryClient,
|
|
27
|
-
queryFn: async () => {
|
|
28
|
-
const res = await fetch(<%- apiUrlExpr %><% if (frontend.auth.function) { %>, {
|
|
29
|
-
headers: { Authorization: <%= frontend.auth.function %>() },
|
|
30
|
-
}<% } %>);
|
|
31
|
-
if (!res.ok) {
|
|
32
|
-
throw new Error(`GET <%= plural %> → ${res.status} ${res.statusText}`);
|
|
33
|
-
}
|
|
34
|
-
return res.json();
|
|
35
|
-
},
|
|
36
|
-
getKey: (item) => item.id,
|
|
37
|
-
schema: <%= schemaPrefix %><%= camelName %>Schema,
|
|
38
|
-
}),
|
|
39
|
-
);
|
|
40
|
-
<% } else { -%>
|
|
41
|
-
export const <%= camelName %>Collection = createCollection(
|
|
42
|
-
electricCollectionOptions({
|
|
43
|
-
id: '<%= plural %>',
|
|
44
|
-
shapeOptions: {
|
|
45
|
-
<% if (frontend.sync.useTableParam) { -%>
|
|
46
|
-
url: new URL(
|
|
47
|
-
'<%= frontend.sync.shapeUrl %>',
|
|
48
|
-
window.location.origin,
|
|
49
|
-
).toString(),
|
|
50
|
-
params: {
|
|
51
|
-
table: '<%= plural %>',
|
|
52
|
-
},
|
|
53
|
-
<% } else { -%>
|
|
54
|
-
<% if (frontend.sync.wrapInUrlConstructor !== false) { -%>
|
|
55
|
-
url: new URL(
|
|
56
|
-
<%- shapeUrlExpr %>,
|
|
57
|
-
window.location.origin,
|
|
58
|
-
).toString(),
|
|
59
|
-
<% } else { -%>
|
|
60
|
-
url: <%- shapeUrlExpr %>,
|
|
61
|
-
<% } -%>
|
|
62
|
-
<% } -%>
|
|
63
|
-
<% if (frontend.auth.function) { -%>
|
|
64
|
-
headers: {
|
|
65
|
-
Authorization: <%= frontend.auth.function %>(),
|
|
66
|
-
},
|
|
67
|
-
<% } -%>
|
|
68
|
-
parser: {
|
|
69
|
-
<% Object.entries(frontend.parsers).forEach(([type, fn]) => { -%>
|
|
70
|
-
<%- type %>: <%- fn %>,
|
|
71
|
-
<% }); -%>
|
|
72
|
-
},
|
|
73
|
-
<% if (frontend.sync.columnMapper) { -%>
|
|
74
|
-
<% if (frontend.sync.columnMapperNeedsCall !== false) { -%>
|
|
75
|
-
columnMapper: <%= frontend.sync.columnMapper %>(),
|
|
76
|
-
<% } else { -%>
|
|
77
|
-
columnMapper: <%= frontend.sync.columnMapper %>,
|
|
78
|
-
<% } -%>
|
|
79
|
-
<% } -%>
|
|
80
|
-
},
|
|
81
|
-
schema: <%= schemaPrefix %><%= camelName %>Schema,
|
|
82
|
-
getKey: (item) => item.id,
|
|
83
|
-
}),
|
|
84
|
-
);
|
|
85
|
-
<% } -%>
|
|
86
|
-
<% } -%>
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: <%= locations.frontendCollections.path %>/collections.ts
|
|
3
|
-
skip_if: <%= !frontendEnabled %>
|
|
4
|
-
force: false
|
|
5
|
-
---
|
|
6
|
-
<% if (frontend.sync.mode === 'api') { -%>
|
|
7
|
-
/**
|
|
8
|
-
* REST-backed Collections (TanStack Query)
|
|
9
|
-
* Generated by entity codegen - do not edit directly
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { queryCollectionOptions } from '@tanstack/query-db-collection';
|
|
13
|
-
import { createCollection } from '@tanstack/react-db';
|
|
14
|
-
import { queryClient } from '<%= frontend.sync.queryClientImport ?? './query-client' %>';
|
|
15
|
-
<% } else { -%>
|
|
16
|
-
/**
|
|
17
|
-
* Electric SQL Collections
|
|
18
|
-
* Generated by entity codegen - do not edit directly
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { electricCollectionOptions } from '@tanstack/electric-db-collection';
|
|
22
|
-
import { createCollection } from '@tanstack/react-db';
|
|
23
|
-
<% if (frontend.sync.columnMapper) { -%>
|
|
24
|
-
import { <%= frontend.sync.columnMapper %> } from '@electric-sql/client';
|
|
25
|
-
<% } -%>
|
|
26
|
-
<% } -%>
|
|
27
|
-
<% if (frontend.auth.function) { -%>
|
|
28
|
-
import { <%= frontend.auth.function %> } from '<%= locations.frontendCollectionsAuth.import %>';
|
|
29
|
-
<% } -%>
|
|
30
|
-
<% if (frontend.sync.apiBaseUrlImport) { -%>
|
|
31
|
-
import { API_BASE_URL } from '<%= frontend.sync.apiBaseUrlImport %>';
|
|
32
|
-
<% } -%>
|
|
33
|
-
import * as schema from '<%= locations.dbSchemaClient.import %>';
|
|
34
|
-
|
|
35
|
-
// Codegen collections
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: "<%= generate.structure === 'entity-first' ? `${locations.frontendGenerated.path}/${generate.fileNaming === 'plural' ? plural : name}/collection.ts` : generate.structure === 'concern-first' ? `${locations.frontendGenerated.path}/collections/${generate.fileNaming === 'plural' ? plural : name}.ts` : '' %>"
|
|
3
|
-
skip_if: <%= !frontendEnabled || (generate.structure === 'monolithic') %>
|
|
4
|
-
force: true
|
|
5
|
-
---
|
|
6
|
-
<%- typeof generatedBanner !== 'undefined' ? generatedBanner : '' %>
|
|
7
|
-
/**
|
|
8
|
-
* <%= className %> Collection
|
|
9
|
-
* Generated by entity codegen - do not edit directly
|
|
10
|
-
*
|
|
11
|
-
* Real-time synced collection using Electric SQL
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
<% if (frontend.sync.columnMapper) { -%>
|
|
15
|
-
import { <%= frontend.sync.columnMapper %> } from '@electric-sql/client';
|
|
16
|
-
<% } -%>
|
|
17
|
-
import { <%= camelName %>Schema } from '<%= locations.dbEntities.import %><% if (!locations.dbEntities.barrelExport) { %>/<%= name %><% } %>';
|
|
18
|
-
<% if (exposeTrpc) { -%>
|
|
19
|
-
import { trpc } from '<%= locations.trpcClient.import %>';
|
|
20
|
-
<% } -%>
|
|
21
|
-
import { electricCollectionOptions } from '@tanstack/electric-db-collection';
|
|
22
|
-
import { createCollection } from '@tanstack/react-db';
|
|
23
|
-
<% if (frontend.auth.function) { -%>
|
|
24
|
-
import { <%= frontend.auth.function %> } from '<%= locations.frontendCollectionsAuth.import %>';
|
|
25
|
-
<% } -%>
|
|
26
|
-
<% if (frontend.sync.apiBaseUrlImport) { -%>
|
|
27
|
-
import { API_BASE_URL } from '<%= frontend.sync.apiBaseUrlImport %>';
|
|
28
|
-
<% } -%>
|
|
29
|
-
<%
|
|
30
|
-
// Collection variable name depends on collectionNaming config
|
|
31
|
-
const collectionVar = generate.collectionNaming === 'plural' ? collectionVarNamePlural : collectionVarName;
|
|
32
|
-
// File name for imports depends on fileNaming config
|
|
33
|
-
const fileName = generate.fileNaming === 'plural' ? plural : name;
|
|
34
|
-
-%>
|
|
35
|
-
<% if (hasSoftDelete) { -%>
|
|
36
|
-
|
|
37
|
-
// TODO: Backend Electric shape endpoint should filter soft-deleted records
|
|
38
|
-
// by including WHERE deleted_at IS NULL in the shape configuration.
|
|
39
|
-
// Without this filter, soft-deleted records will reappear after Electric sync.
|
|
40
|
-
<% } -%>
|
|
41
|
-
|
|
42
|
-
export const <%= collectionVar %> = createCollection(
|
|
43
|
-
electricCollectionOptions({
|
|
44
|
-
id: '<%= plural %>',
|
|
45
|
-
shapeOptions: {
|
|
46
|
-
<% if (frontend.sync.useTableParam) { -%>
|
|
47
|
-
<% if (frontend.sync.wrapInUrlConstructor !== false) { -%>
|
|
48
|
-
url: new URL(
|
|
49
|
-
'<%= frontend.sync.shapeUrl %>',
|
|
50
|
-
typeof window !== 'undefined'
|
|
51
|
-
? window.location.origin
|
|
52
|
-
: 'http://localhost:5173',
|
|
53
|
-
).toString(),
|
|
54
|
-
<% } else { -%>
|
|
55
|
-
url: '<%= frontend.sync.shapeUrl %>',
|
|
56
|
-
<% } -%>
|
|
57
|
-
params: {
|
|
58
|
-
table: '<%= plural %>',
|
|
59
|
-
},
|
|
60
|
-
<% } else { -%>
|
|
61
|
-
<% if (frontend.sync.wrapInUrlConstructor !== false) { -%>
|
|
62
|
-
url: new URL(
|
|
63
|
-
`<%= frontend.sync.shapeUrl %>/<%= plural %>`,
|
|
64
|
-
typeof window !== 'undefined'
|
|
65
|
-
? window.location.origin
|
|
66
|
-
: 'http://localhost:5173',
|
|
67
|
-
).toString(),
|
|
68
|
-
<% } else { -%>
|
|
69
|
-
url: `<%= frontend.sync.shapeUrl %>/<%= plural %>`,
|
|
70
|
-
<% } -%>
|
|
71
|
-
<% } -%>
|
|
72
|
-
<% if (frontend.auth.function) { -%>
|
|
73
|
-
headers: {
|
|
74
|
-
Authorization: <%= frontend.auth.function %>(),
|
|
75
|
-
},
|
|
76
|
-
<% } -%>
|
|
77
|
-
parser: {
|
|
78
|
-
<% Object.entries(frontend.parsers).forEach(([type, fn]) => { -%>
|
|
79
|
-
<%- type %>: <%- fn %>,
|
|
80
|
-
<% }); -%>
|
|
81
|
-
},
|
|
82
|
-
<% if (frontend.sync.columnMapper) { -%>
|
|
83
|
-
<% if (frontend.sync.columnMapperNeedsCall !== false) { -%>
|
|
84
|
-
columnMapper: <%= frontend.sync.columnMapper %>(),
|
|
85
|
-
<% } else { -%>
|
|
86
|
-
columnMapper: <%= frontend.sync.columnMapper %>,
|
|
87
|
-
<% } -%>
|
|
88
|
-
<% } -%>
|
|
89
|
-
},
|
|
90
|
-
schema: <%= camelName %>Schema,
|
|
91
|
-
getKey: (item) => item.id,
|
|
92
|
-
<% if (generate.mutations && (exposeTrpc || exposeRepository)) { -%>
|
|
93
|
-
<% if (exposeTrpc) { -%>
|
|
94
|
-
onInsert: async ({ transaction }) => {
|
|
95
|
-
const { modified } = transaction.mutations[0];
|
|
96
|
-
const result = await trpc.<%= plural %>.create.mutate(modified);
|
|
97
|
-
return { txid: result.txid };
|
|
98
|
-
},
|
|
99
|
-
onUpdate: async ({ transaction }) => {
|
|
100
|
-
const { modified } = transaction.mutations[0];
|
|
101
|
-
const result = await trpc.<%= plural %>.update.mutate({ id: modified.id, data: modified });
|
|
102
|
-
return { txid: result.txid };
|
|
103
|
-
},
|
|
104
|
-
onDelete: async ({ transaction }) => {
|
|
105
|
-
const { original } = transaction.mutations[0];
|
|
106
|
-
const result = await trpc.<%= plural %>.delete.mutate({ id: original.id });
|
|
107
|
-
return { txid: result.txid };
|
|
108
|
-
},
|
|
109
|
-
<% } -%>
|
|
110
|
-
<% } -%>
|
|
111
|
-
}),
|
|
112
|
-
);
|
|
113
|
-
<%
|
|
114
|
-
// Collect unique belongs_to targets for imports (FK resolution)
|
|
115
|
-
// Only import if fkResolution is enabled (default: true)
|
|
116
|
-
const importedEntities = new Set();
|
|
117
|
-
if (generate.fkResolution !== false) {
|
|
118
|
-
existingBelongsTo.forEach((rel) => {
|
|
119
|
-
if (rel.target !== name) {
|
|
120
|
-
importedEntities.add(rel.target);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
-%>
|
|
125
|
-
<% if (importedEntities.size > 0) { -%>
|
|
126
|
-
|
|
127
|
-
// Import related collections for FK resolution
|
|
128
|
-
<% importedEntities.forEach((target) => {
|
|
129
|
-
const targetCamel = target.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
130
|
-
// Simple pluralization matching prompt.js pluralize function
|
|
131
|
-
const targetPlural = target.endsWith('y') ? target.slice(0, -1) + 'ies' :
|
|
132
|
-
(target.endsWith('s') || target.endsWith('x') || target.endsWith('ch') || target.endsWith('sh')) ? target + 'es' : target + 's';
|
|
133
|
-
const targetFileName = generate.fileNaming === 'plural' ? targetPlural : target;
|
|
134
|
-
const targetCamelPlural = targetCamel.endsWith('y') ? targetCamel.slice(0, -1) + 'ies' : targetCamel + 's';
|
|
135
|
-
-%>
|
|
136
|
-
import { <%= generate.collectionNaming === 'plural' ? targetCamelPlural : targetCamel %>Collection } from '<% if (generate.structure === 'entity-first') { %>./<%= targetFileName %>/collection<% } else { %>./<%= targetFileName %><% } %>';
|
|
137
|
-
<% }); -%>
|
|
138
|
-
<% } -%>
|
|
139
|
-
<% if (generate.structure === 'entity-first') { -%>
|
|
140
|
-
|
|
141
|
-
// Import types from sibling module
|
|
142
|
-
import type { <%= className %>, <%= className %>Resolved } from './types';
|
|
143
|
-
<% } else { -%>
|
|
144
|
-
|
|
145
|
-
// Import types
|
|
146
|
-
import type { <%= className %>, <%= className %>Resolved } from '<% if (generate.structure === 'concern-first') { %>../types/<%= fileName %><% } %>';
|
|
147
|
-
<% } -%>
|
|
148
|
-
|
|
149
|
-
// ============================================================================
|
|
150
|
-
// Resolution (FK lookup - internal use)
|
|
151
|
-
// ============================================================================
|
|
152
|
-
<% if (existingBelongsTo.length > 0 && generate.fkResolution !== false) { -%>
|
|
153
|
-
|
|
154
|
-
export function resolveRelations(entity: <%= className %>): <%= className %>Resolved {
|
|
155
|
-
return {
|
|
156
|
-
...entity,
|
|
157
|
-
<% existingBelongsTo.forEach((rel) => {
|
|
158
|
-
const targetCamel = rel.target.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
159
|
-
const targetCamelPlural = targetCamel.endsWith('y') ? targetCamel.slice(0, -1) + 'ies' : targetCamel + 's';
|
|
160
|
-
const targetCollectionVar = generate.collectionNaming === 'plural' ? targetCamelPlural : targetCamel;
|
|
161
|
-
-%>
|
|
162
|
-
<%= rel.name %>: entity.<%= rel.foreignKeyCamel %>
|
|
163
|
-
? <%= targetCollectionVar %>Collection.state.get(entity.<%= rel.foreignKeyCamel %>)
|
|
164
|
-
: undefined,
|
|
165
|
-
<% }); -%>
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
<% } else { -%>
|
|
169
|
-
|
|
170
|
-
export function resolveRelations(entity: <%= className %>): <%= className %>Resolved {
|
|
171
|
-
return entity;
|
|
172
|
-
}
|
|
173
|
-
<% } -%>
|