@lunora/codegen 1.0.0-alpha.5 → 1.0.0-alpha.50
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 +6 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +1950 -734
- package/dist/index.d.ts +1950 -734
- package/dist/index.mjs +32 -24
- package/dist/packem_shared/AGENTS_FILENAME-Cxx7wiSs.mjs +158 -0
- package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DjpXMqhp.mjs} +1 -1
- package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DyQ5FwkM.mjs} +7 -5
- package/dist/packem_shared/FLAGS_FILENAME-Dg4mKUuz.mjs +139 -0
- package/dist/packem_shared/GENERATED_HEADER-C5paWNYO.mjs +4 -0
- package/dist/packem_shared/{LUNORA_ERROR_CODES-CySpQPD3.mjs → LUNORA_ERROR_CODES-DvTLozCu.mjs} +8 -1
- package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +81 -0
- package/dist/packem_shared/{OPENRPC_VERSION-C7zKVeKi.mjs → OPENRPC_VERSION-7A2weq2u.mjs} +2 -2
- package/dist/packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs +145 -0
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-G5dkcxPR.mjs +3502 -0
- package/dist/packem_shared/{SCHEMA_SNAPSHOT_VERSION-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs} +18 -2
- package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +94 -0
- package/dist/packem_shared/WORKFLOWS_FILENAME-Dtuzvluc.mjs +158 -0
- package/dist/packem_shared/{buildOpenApiDocument-Cns1EkCQ.mjs → buildOpenApiDocument-B7mEdzA2.mjs} +2 -2
- package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-Dx3K42rk.mjs} +1 -1
- package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-DvqkEWdx.mjs} +68 -36
- package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-BJ-qR7Rg.mjs} +72 -14
- package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-daCzuTe8.mjs} +10 -2
- package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-DI4q5NxE.mjs} +24 -2
- package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-BcTOEKNU.mjs} +58 -2
- package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-VNUFvCwr.mjs} +10 -4
- package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-S0N2xLCq.mjs} +1 -1
- package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-CJnnnLpd.mjs} +1 -1
- package/dist/packem_shared/{discoverR2sqlCalls-BpDqvcUn.mjs → discoverR2sqlCalls-pnpicWfz.mjs} +1 -1
- package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-DppniPUH.mjs} +1 -1
- package/dist/packem_shared/discoverSandboxUsage-BUM2r90k.mjs +38 -0
- package/dist/packem_shared/{discoverSchema-DB_QrIQe.mjs → discoverSchema-BaSAvooG.mjs} +326 -14
- package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-CnHl2rXD.mjs} +1 -1
- package/dist/packem_shared/{GENERATED_HEADER-DF1hQcix.mjs → emit-ClTbCLy4.mjs} +1458 -154
- package/dist/packem_shared/{emitApp-Ci_hcJNO.mjs → emitApp-CzzrjVqH.mjs} +129 -31
- package/dist/packem_shared/formatAdvisories-BmF7Mplc.mjs +115 -0
- package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-BSJo1HGP.mjs} +18 -6
- package/dist/packem_shared/redact-oTmsol5A.mjs +33 -0
- package/package.json +10 -7
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-BqOtU2lC.mjs +0 -922
- package/dist/packem_shared/WORKFLOWS_FILENAME-DRDQdhfq.mjs +0 -84
- package/dist/packem_shared/formatAdvisories-8NIv1k0I.mjs +0 -69
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
import { GENERATED_HEADER } from './
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[
|
|
8
|
-
"hasHyperdrive",
|
|
9
|
-
"hyperdrive",
|
|
10
|
-
"sql",
|
|
11
|
-
"Wire the Hyperdrive SQL client backing `ctx.sql` — build it with `createHyperdrive` + `fromPostgresJs`/`fromNodePg`/`fromMysql2`."
|
|
12
|
-
],
|
|
13
|
-
["hasImages", "images", "images", "Override the Images binding backing `ctx.images` (defaults to `env.IMAGES`)."],
|
|
14
|
-
["hasKv", "kv", "kv", "Override the Workers KV binding backing `ctx.kv` (defaults to `env.KV`)."],
|
|
15
|
-
["hasPayments", "payment", "payment", "Wire the payment options backing `ctx.payments`."],
|
|
16
|
-
[
|
|
17
|
-
"hasR2sql",
|
|
18
|
-
"r2sql",
|
|
19
|
-
"r2sql",
|
|
20
|
-
"Wire the R2 SQL client backing `ctx.r2sql` — build it with `createR2Sql({ accountId, apiToken, bucket })` (defaults to env `R2_SQL_TOKEN` / `R2_SQL_ACCOUNT_ID` / `R2_SQL_BUCKET`)."
|
|
21
|
-
],
|
|
22
|
-
["hasVectors", "vectors", "vectors", "Wire the Vectorize index map backing `ctx.vectors`."]
|
|
23
|
-
];
|
|
1
|
+
import { A as APP_METHOD_CAPABILITIES, G as GENERATED_HEADER } from './emit-ClTbCLy4.mjs';
|
|
2
|
+
|
|
3
|
+
const hasFlagKey = (key) => `has${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
4
|
+
const LONG_TAIL = APP_METHOD_CAPABILITIES.map(
|
|
5
|
+
({ appMethod, key }) => [hasFlagKey(key), appMethod.method, appMethod.configKey, appMethod.doc]
|
|
6
|
+
);
|
|
24
7
|
const hasAnyLongTail = (options) => LONG_TAIL.some(([flag]) => options[flag]);
|
|
8
|
+
const buildIdentityImports = (identity) => identity ? [`import * as lunoraIdentityContract from "../identity.js";`] : [];
|
|
9
|
+
const buildAccessImports = (hasAccess, hasAuth) => hasAccess ? [
|
|
10
|
+
`import type { CreateAccessResolverOptions } from "@lunora/cloudflare-access";`,
|
|
11
|
+
`import { createAccessResolver${hasAuth ? ", composeResolvers" : ""} } from "@lunora/cloudflare-access";`
|
|
12
|
+
] : [];
|
|
13
|
+
const buildKvImports = (hasKv) => hasKv ? [`import { createKvIntrospectorFromEnv } from "@lunora/bindings/kv";`] : [];
|
|
14
|
+
const hasEmailAgents = (options) => (options.emailAgents?.length ?? 0) > 0;
|
|
15
|
+
const buildInboundImports = (options) => hasEmailAgents(options) ? [`import { dispatchAgentEmail } from "@lunora/agent/inbound";`] : [];
|
|
16
|
+
const buildAgentDefinitionsImport = (options) => hasEmailAgents(options) ? [`import * as lunoraAgentDefinitions from "../agents.js";`] : [];
|
|
25
17
|
const buildImportLines = (options) => {
|
|
26
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
hasAccess,
|
|
20
|
+
hasAuth,
|
|
21
|
+
hasFramework,
|
|
22
|
+
hasGlobal,
|
|
23
|
+
hasHyperdriveGlobal,
|
|
24
|
+
hasKv,
|
|
25
|
+
hasQueue,
|
|
26
|
+
hasScheduler,
|
|
27
|
+
hasStorage,
|
|
28
|
+
hasWorkflow,
|
|
29
|
+
useUmbrella,
|
|
30
|
+
wantsOpenApi,
|
|
31
|
+
wantsOpenRpc
|
|
32
|
+
} = options;
|
|
27
33
|
const runtimeModule = useUmbrella ? "lunorash/runtime" : "@lunora/runtime";
|
|
28
34
|
const runtimeTypeImports = ["ExecutionContextLike", "LunoraWorker", "Route", "ScheduledControllerLike", "ShardNamespaceLike", "WorkerOptions"];
|
|
29
35
|
if (hasGlobal) {
|
|
@@ -35,6 +41,7 @@ const buildImportLines = (options) => {
|
|
|
35
41
|
const runtimeValueImports = [
|
|
36
42
|
...hasGlobal || hasHyperdriveGlobal ? ["createCrossShardRelationCapabilities"] : [],
|
|
37
43
|
"createWorker",
|
|
44
|
+
"resolveLogArchiveFromEnv",
|
|
38
45
|
...hasFramework ? ["withFrameworkWorker"] : []
|
|
39
46
|
].join(", ");
|
|
40
47
|
return [
|
|
@@ -42,6 +49,7 @@ const buildImportLines = (options) => {
|
|
|
42
49
|
`import type { LunoraAuth, LunoraAuthOptions } from "@lunora/auth";`,
|
|
43
50
|
`import { createAuth, createAuthAdmin, ensureMigrated, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";`
|
|
44
51
|
] : [],
|
|
52
|
+
...buildAccessImports(hasAccess, hasAuth),
|
|
45
53
|
...hasGlobal ? [
|
|
46
54
|
`import type { D1CtxDbOptions, D1DatabaseLike, D1Exec } from "@lunora/d1";`,
|
|
47
55
|
`import { createD1CtxDb, facetGlobalColumn, listGlobalTables, readGlobalTablePage } from "@lunora/d1";`
|
|
@@ -51,15 +59,23 @@ const buildImportLines = (options) => {
|
|
|
51
59
|
`import { createHyperdriveGlobalCtxDb } from "@lunora/hyperdrive/global";`,
|
|
52
60
|
`import type { SqlCtxDbOptions, SqlExec } from "@lunora/sql-store";`
|
|
53
61
|
] : [],
|
|
62
|
+
...buildKvImports(hasKv),
|
|
54
63
|
...hasScheduler ? [`import type { DurableObjectNamespaceLike } from "@lunora/scheduler";`, `import { createScheduler } from "@lunora/scheduler";`] : [],
|
|
55
64
|
...hasStorage ? [`import type { R2BucketLike, Storage } from "@lunora/storage";`, `import { createBucketStorage, createStorage } from "@lunora/storage";`] : [],
|
|
56
65
|
...hasWorkflow ? [`import { createWorkflowsRestClient } from "@lunora/workflow";`] : [],
|
|
66
|
+
...buildInboundImports(options),
|
|
57
67
|
`import type { ${[...runtimeTypeImports].toSorted((a, b) => a.localeCompare(b)).join(", ")} } from "${runtimeModule}";`,
|
|
58
68
|
`import { ${runtimeValueImports} } from "${runtimeModule}";`,
|
|
59
69
|
``,
|
|
70
|
+
...buildIdentityImports(options.identity),
|
|
71
|
+
...buildAgentDefinitionsImport(options),
|
|
60
72
|
...hasGlobal || hasHyperdriveGlobal ? [`import schema from "../schema.js";`] : [],
|
|
61
73
|
`import { LUNORA_CRONS } from "./crons.js";`,
|
|
62
74
|
`import { LUNORA_FUNCTIONS } from "./functions.js";`,
|
|
75
|
+
...hasQueue ? [
|
|
76
|
+
`import { createQueueCaptureSink, dispatchQueueBatch, shouldCaptureQueue } from "@lunora/queue";`,
|
|
77
|
+
`import { LUNORA_QUEUE_REGISTRY } from "./queues.js";`
|
|
78
|
+
] : [],
|
|
63
79
|
...wantsOpenApi ? [`import { openApiSpec } from "./openapi.js";`] : [],
|
|
64
80
|
...wantsOpenRpc ? [`import { openRpcSpec } from "./openrpc.js";`] : [],
|
|
65
81
|
`import { createShardDO } from "./shard.js";`
|
|
@@ -119,9 +135,10 @@ interface AuthDeclaration<Env> {
|
|
|
119
135
|
] : []
|
|
120
136
|
];
|
|
121
137
|
const buildFieldLines = (options) => [
|
|
138
|
+
...options.hasAccess ? [` private accessSelector?: Selector<Env, CreateAccessResolverOptions>;`] : [],
|
|
122
139
|
` private adminToken?: Selector<Env, string>;`,
|
|
123
140
|
...options.hasAuth ? [` private authDeclaration?: AuthDeclaration<Env>;`] : [],
|
|
124
|
-
` private readonly extendFns: ((env: Env) => Partial<WorkerOptions>)[] = [];`,
|
|
141
|
+
` private readonly extendFns: ((env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>)[] = [];`,
|
|
125
142
|
...options.hasGlobal ? [` private globalDeclaration?: GlobalDeclaration<Env>;`] : [],
|
|
126
143
|
...options.hasHyperdriveGlobal ? [` private hyperdriveGlobalDeclaration?: HyperdriveGlobalDeclaration<Env>;`] : [],
|
|
127
144
|
` private readonly routeMap: Record<string, Route> = {};`,
|
|
@@ -139,6 +156,14 @@ const buildLongTailMethods = (options) => LONG_TAIL.filter(([flag]) => options[f
|
|
|
139
156
|
}`
|
|
140
157
|
);
|
|
141
158
|
const buildMethodBlocks = (options) => [
|
|
159
|
+
...options.hasAccess ? [
|
|
160
|
+
` /** Wire Cloudflare Access (Zero Trust) — verifies the \`Cf-Access-Jwt-Assertion\` JWT and feeds the identity into \`ctx.auth\` / RLS via \`resolveIdentity\`. When \`.auth(...)\` is also configured, Access is composed ahead of it (Access wins when its JWT is present; everyone else falls through to the app session). */
|
|
161
|
+
public access(selector: Selector<Env, CreateAccessResolverOptions>): this {
|
|
162
|
+
this.accessSelector = selector;
|
|
163
|
+
|
|
164
|
+
return this;
|
|
165
|
+
}`
|
|
166
|
+
] : [],
|
|
142
167
|
` /** Bearer token gating the \`/_lunora/admin/*\` endpoints the studio calls. */
|
|
143
168
|
public admin(selector: Selector<Env, string>): this {
|
|
144
169
|
this.adminToken = selector;
|
|
@@ -153,8 +178,8 @@ const buildMethodBlocks = (options) => [
|
|
|
153
178
|
return this;
|
|
154
179
|
}`
|
|
155
180
|
] : [],
|
|
156
|
-
` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. */
|
|
157
|
-
public extend(fn: (env: Env) => Partial<WorkerOptions>): this {
|
|
181
|
+
` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. The second \`derived\` argument is a snapshot of the options assembled so far (after \`.auth(...)\` etc.), so you can compose rather than clobber — e.g. wrap \`derived.resolveIdentity\` instead of replacing it. */
|
|
182
|
+
public extend(fn: (env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>): this {
|
|
158
183
|
this.extendFns.push(fn);
|
|
159
184
|
|
|
160
185
|
return this;
|
|
@@ -274,7 +299,7 @@ const buildShardFactoryBody = (options) => {
|
|
|
274
299
|
const namespace = this.schedulerDeclaration?.namespace(env);
|
|
275
300
|
const origin = this.schedulerDeclaration?.origin?.(env);
|
|
276
301
|
|
|
277
|
-
return namespace && origin ? createScheduler({ namespace, originUrl: origin }) : undefined;
|
|
302
|
+
return namespace && origin ? createScheduler({${options.jurisdiction ? ` jurisdiction: ${JSON.stringify(options.jurisdiction)},` : ""} namespace, originUrl: origin }) : undefined;
|
|
278
303
|
},
|
|
279
304
|
}
|
|
280
305
|
: {}),`
|
|
@@ -321,6 +346,17 @@ const buildWorkerOptionLines = (options) => [
|
|
|
321
346
|
Object.assign(options, this.buildStorageAdmin(env));
|
|
322
347
|
}`
|
|
323
348
|
] : [],
|
|
349
|
+
// The studio's KV browser is wired zero-config: `createKvIntrospectorFromEnv`
|
|
350
|
+
// scans `env` for every bound Workers KV namespace, so each `kv_namespaces`
|
|
351
|
+
// entry in wrangler.jsonc appears under its binding name (any name, any count)
|
|
352
|
+
// with no manual `createKvIntrospector` call. A deployment with no KV binding
|
|
353
|
+
// yields an empty namespace list rather than crashing.
|
|
354
|
+
...options.hasKv ? [` options.kvIntrospector = createKvIntrospectorFromEnv(env);`] : [],
|
|
355
|
+
// The studio's Logs → Archive feed is wired zero-config: when the operator sets
|
|
356
|
+
// `LUNORA_LOG_ARCHIVE_TABLE` (the R2 Data Catalog table `pipelineLogSink` writes
|
|
357
|
+
// to), the durable archive becomes readable; unset ⇒ `undefined` ⇒ the feed
|
|
358
|
+
// reports "not configured". The R2 SQL credentials come from `R2_SQL_*` env vars.
|
|
359
|
+
` options.logArchive = resolveLogArchiveFromEnv(env);`,
|
|
324
360
|
...options.hasAuth ? [
|
|
325
361
|
` if (this.authDeclaration) {
|
|
326
362
|
options.authHandler = (request) => {
|
|
@@ -343,13 +379,64 @@ const buildWorkerOptionLines = (options) => [
|
|
|
343
379
|
|
|
344
380
|
options.authAdmin = authInstance ? createAuthAdmin(authInstance) : undefined;
|
|
345
381
|
}`
|
|
382
|
+
] : [],
|
|
383
|
+
// Cloudflare Access — runs AFTER the auth block so it can compose ahead of
|
|
384
|
+
// the better-auth resolver rather than clobber it. With `.auth()` present,
|
|
385
|
+
// a request carrying a verified Access JWT is authenticated by Access and
|
|
386
|
+
// everyone else falls through to the app session; without it, Access is the
|
|
387
|
+
// sole resolver.
|
|
388
|
+
...options.hasAccess ? [
|
|
389
|
+
options.hasAuth ? ` if (this.accessSelector) {
|
|
390
|
+
const accessResolver = createAccessResolver(this.accessSelector(env));
|
|
391
|
+
const fallback = options.resolveIdentity;
|
|
392
|
+
|
|
393
|
+
options.resolveIdentity = fallback ? composeResolvers(accessResolver, fallback) : accessResolver;
|
|
394
|
+
}` : ` if (this.accessSelector) {
|
|
395
|
+
options.resolveIdentity = createAccessResolver(this.accessSelector(env));
|
|
396
|
+
}`
|
|
397
|
+
] : [],
|
|
398
|
+
// Voice-enabled agents: map each export name to its `VOICE_*` Durable Object
|
|
399
|
+
// namespace so the runtime serves `/_lunora/voice/<exportName>`. Read off
|
|
400
|
+
// `env` structurally (the binding is provisioned by the config layer's
|
|
401
|
+
// reconcile step, so it may not be on the generated `Env` type). Emitted only
|
|
402
|
+
// when at least one agent opted into voice — voice-free output is unchanged.
|
|
403
|
+
...options.voiceAgents && options.voiceAgents.length > 0 ? [
|
|
404
|
+
` options.voiceAgents = {
|
|
405
|
+
${options.voiceAgents.map(
|
|
406
|
+
(agent) => ` ${JSON.stringify(agent.exportName)}: (env as Record<string, unknown>)[${JSON.stringify(agent.bindingName)}] as ShardNamespaceLike,`
|
|
407
|
+
).join("\n")}
|
|
408
|
+
};`
|
|
346
409
|
] : []
|
|
347
410
|
];
|
|
348
411
|
const buildBaseWorkerOptions = (options) => [
|
|
349
412
|
` cronJobs: LUNORA_CRONS,`,
|
|
350
413
|
` functions: LUNORA_FUNCTIONS,`,
|
|
414
|
+
// The declared `defineIdentity(...)` contract — wires the runtime trust
|
|
415
|
+
// boundary so `wrapResolverWithContract` validates every resolved identity
|
|
416
|
+
// against it before it becomes `ctx.auth`. Emitted only when the app declares
|
|
417
|
+
// a contract, so apps without one keep unchanged output.
|
|
418
|
+
...options.identity ? [` identity: lunoraIdentityContract.${options.identity.exportName},`] : [],
|
|
419
|
+
// Schema `.jurisdiction("…")` pins every DO the worker reaches to the
|
|
420
|
+
// Cloudflare data-residency region. Emitted only when declared, so apps
|
|
421
|
+
// without it keep the un-pinned global namespace (and unchanged output).
|
|
422
|
+
...options.jurisdiction ? [` jurisdiction: ${JSON.stringify(options.jurisdiction)},`] : [],
|
|
351
423
|
...options.wantsOpenApi ? [` openApiSpec,`] : [],
|
|
352
424
|
...options.wantsOpenRpc ? [` openRpcSpec,`] : [],
|
|
425
|
+
// The push-consumer handler backing the worker's `queue(batch, …)` entry:
|
|
426
|
+
// routes each delivered batch to its `defineQueue` handler. Built from
|
|
427
|
+
// `@lunora/queue` here (keeping the runtime decoupled) and wired only when the
|
|
428
|
+
// app declares push queues in `lunora/queues.ts`. In a dev environment (or with
|
|
429
|
+
// `LUNORA_QUEUE_CAPTURE`), every consumed message is recorded into the studio's
|
|
430
|
+
// Queues log via the root shard's `recordQueueMessage` admin RPC.
|
|
431
|
+
...options.hasQueue ? [
|
|
432
|
+
` queue: (batch: unknown, queueEnv: unknown, _context: ExecutionContextLike): Promise<void> =>`,
|
|
433
|
+
` dispatchQueueBatch(batch as Parameters<typeof dispatchQueueBatch>[0], LUNORA_QUEUE_REGISTRY, {`,
|
|
434
|
+
` capture: shouldCaptureQueue(queueEnv as Record<string, unknown>)`,
|
|
435
|
+
` ? createQueueCaptureSink(queueEnv as Record<string, unknown>${options.jurisdiction ? `, { jurisdiction: ${JSON.stringify(options.jurisdiction)} }` : ""})`,
|
|
436
|
+
` : undefined,`,
|
|
437
|
+
` env: queueEnv as Record<string, unknown>,`,
|
|
438
|
+
` }),`
|
|
439
|
+
] : [],
|
|
353
440
|
` routes: this.routeMap,`,
|
|
354
441
|
` shardDO: this.shardSelector?.(env) ?? (undefined as unknown as ShardNamespaceLike),`
|
|
355
442
|
];
|
|
@@ -490,6 +577,12 @@ const emitApp = (options) => {
|
|
|
490
577
|
const buildWorkerLine = options.hasFramework ? ` const buildWorker = (env: Env): LunoraWorker =>
|
|
491
578
|
host ? withFrameworkWorker(host, (hostEnv) => this.buildWorkerOptions(hostEnv as Env, ${getAuthArgument})) : createWorker(this.buildWorkerOptions(env, ${getAuthArgument}));` : ` const buildWorker = (env: Env): LunoraWorker => createWorker(this.buildWorkerOptions(env, ${getAuthArgument}));`;
|
|
492
579
|
const assembleParameter = options.hasFramework ? `host?: FrameworkHostHandler` : ``;
|
|
580
|
+
const emailAgents = options.emailAgents ?? [];
|
|
581
|
+
const emailAgentsBlock = emailAgents.length > 0 ? ` composed.email = dispatchAgentEmail([
|
|
582
|
+
${emailAgents.map((agent) => ` { agent: lunoraAgentDefinitions.${agent.exportName}, binding: ${JSON.stringify(agent.bindingName)} },`).join("\n")}
|
|
583
|
+
]);
|
|
584
|
+
|
|
585
|
+
` : "";
|
|
493
586
|
const buildTerminals = ` /** Materialise the standalone Cloudflare worker + \`ShardDO\` class. */
|
|
494
587
|
public build(): ComposedApp {
|
|
495
588
|
return this.assemble();
|
|
@@ -558,10 +651,15 @@ ${buildWorkerLine}
|
|
|
558
651
|
worker ??= buildWorker(rawEnv as Env);
|
|
559
652
|
|
|
560
653
|
return worker.serverQuery(request, rawEnv, reference, args, options);
|
|
561
|
-
}
|
|
654
|
+
},${options.hasQueue ? `
|
|
655
|
+
queue: async (batch: unknown, rawEnv: unknown, context: ExecutionContextLike): Promise<void> => {
|
|
656
|
+
worker ??= buildWorker(rawEnv as Env);
|
|
657
|
+
|
|
658
|
+
return worker.queue?.(batch, rawEnv, context);
|
|
659
|
+
},` : ""}
|
|
562
660
|
};
|
|
563
661
|
|
|
564
|
-
if (this.emailHandler) {
|
|
662
|
+
${emailAgentsBlock} if (this.emailHandler) {
|
|
565
663
|
const handler = this.emailHandler;
|
|
566
664
|
|
|
567
665
|
composed.email = (message, rawEnv, context) => handler(rawEnv as Env)(message, rawEnv, context);
|
|
@@ -581,7 +679,7 @@ ${buildBaseWorkerOptions(options).join("\n")}
|
|
|
581
679
|
}
|
|
582
680
|
|
|
583
681
|
${workerOptionLines.join("\n\n")}${workerOptionLines.length > 0 ? "\n\n" : ""} for (const fn of this.extendFns) {
|
|
584
|
-
Object.assign(options, fn(env));
|
|
682
|
+
Object.assign(options, fn(env, { ...options }));
|
|
585
683
|
}
|
|
586
684
|
|
|
587
685
|
return options;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { runAdvisor } from '@lunora/advisor';
|
|
2
|
+
|
|
3
|
+
const flattenIndexes = (table) => [
|
|
4
|
+
...table.indexes.map((index) => {
|
|
5
|
+
return { fields: index.fields, kind: "index", name: index.name, unique: index.unique };
|
|
6
|
+
}),
|
|
7
|
+
...table.searchIndexes.map((index) => {
|
|
8
|
+
return { fields: [index.field, ...index.filterFields ?? []], kind: "search", name: index.name };
|
|
9
|
+
}),
|
|
10
|
+
...table.rankIndexes.map((index) => {
|
|
11
|
+
return { fields: [...index.sortBy.map((key) => key.field), ...index.partitionBy ?? []], kind: "rank", name: index.name };
|
|
12
|
+
}),
|
|
13
|
+
...table.vectorIndexes.filter((index) => index.field !== void 0).map((index) => {
|
|
14
|
+
return { fields: [index.field], kind: "vector", name: index.name };
|
|
15
|
+
})
|
|
16
|
+
];
|
|
17
|
+
const toAdvisorSchema = (schema) => {
|
|
18
|
+
return {
|
|
19
|
+
rlsMode: schema.rlsMode,
|
|
20
|
+
tables: schema.tables.map((table) => {
|
|
21
|
+
return {
|
|
22
|
+
externallyManaged: table.externallyManaged ?? false,
|
|
23
|
+
externalSource: table.externalSource ? {
|
|
24
|
+
hasReconcile: table.externalSource.hasReconcile ?? false,
|
|
25
|
+
hasSoftDelete: table.externalSource.hasSoftDelete ?? false,
|
|
26
|
+
hasTenantBy: table.externalSource.hasTenantBy,
|
|
27
|
+
mode: table.externalSource.mode,
|
|
28
|
+
unanalyzable: table.externalSource.unanalyzable
|
|
29
|
+
} : void 0,
|
|
30
|
+
fields: Object.keys(table.shape),
|
|
31
|
+
indexes: flattenIndexes(table),
|
|
32
|
+
isPublic: table.isPublic ?? false,
|
|
33
|
+
name: table.name,
|
|
34
|
+
relations: table.relations.map((relation) => {
|
|
35
|
+
return {
|
|
36
|
+
field: relation.field,
|
|
37
|
+
kind: relation.kind,
|
|
38
|
+
name: relation.name,
|
|
39
|
+
onDelete: relation.onDelete,
|
|
40
|
+
references: relation.references,
|
|
41
|
+
table: relation.table
|
|
42
|
+
};
|
|
43
|
+
}),
|
|
44
|
+
shardKind: typeof table.shardMode === "string" ? table.shardMode : "shardBy",
|
|
45
|
+
softDelete: table.softDelete
|
|
46
|
+
};
|
|
47
|
+
})
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const toAdvisorShapes = (shapes) => shapes.map((shape) => {
|
|
51
|
+
return { exportName: shape.exportName, file: `lunora/${shape.filePath}.ts`, table: shape.table };
|
|
52
|
+
});
|
|
53
|
+
const lintSchema = (options) => runAdvisor(
|
|
54
|
+
{
|
|
55
|
+
adminRoutes: options.adminRoutes,
|
|
56
|
+
aiRawRuns: options.aiRawRuns,
|
|
57
|
+
aiToolSideEffects: options.aiToolSideEffects,
|
|
58
|
+
argumentDerivedFetches: options.argumentDerivedFetches,
|
|
59
|
+
argValidators: options.argumentValidators,
|
|
60
|
+
authApiCalls: options.authApiCalls,
|
|
61
|
+
authConfigs: options.authConfigs,
|
|
62
|
+
browserUrlAccesses: options.browserUrlAccesses,
|
|
63
|
+
configCalls: options.configCalls,
|
|
64
|
+
containerKeyAccesses: options.containerKeyAccesses,
|
|
65
|
+
containerOverrides: options.containerOverrides,
|
|
66
|
+
containers: options.containers,
|
|
67
|
+
failOpenGuards: options.failOpenGuards,
|
|
68
|
+
flagSecurityDefaults: options.flagSecurityDefaults,
|
|
69
|
+
httpActionGuards: options.httpActionGuards,
|
|
70
|
+
httpHeaderWrites: options.httpHeaderWrites,
|
|
71
|
+
identityClaimReads: options.identityClaimReads,
|
|
72
|
+
imageDeliveryUrlAccesses: options.imageDeliveryUrlAccesses,
|
|
73
|
+
inserts: options.inserts,
|
|
74
|
+
kvKeyAccesses: options.kvKeyAccesses,
|
|
75
|
+
mailRecipientAccesses: options.mailRecipientAccesses,
|
|
76
|
+
maskProcedures: options.maskProcedures,
|
|
77
|
+
maskStrategies: options.maskStrategies,
|
|
78
|
+
mutatorWrites: options.mutatorWrites,
|
|
79
|
+
nondeterministicCalls: options.nondeterministicCalls,
|
|
80
|
+
normalizeIdAuthorizations: options.normalizeIdAuthorizations,
|
|
81
|
+
ownerFieldWrites: options.ownerFieldWrites,
|
|
82
|
+
paymentWebhooks: options.paymentWebhooks,
|
|
83
|
+
privilegedDispatches: options.privilegedDispatches,
|
|
84
|
+
procedureProtections: options.procedureProtections,
|
|
85
|
+
queries: options.queries ?? [],
|
|
86
|
+
queues: options.queues,
|
|
87
|
+
r2sqlCalls: options.r2sqlCalls,
|
|
88
|
+
ratelimitKeySelectors: options.ratelimitKeySelectors,
|
|
89
|
+
rawRowReturns: options.rawRowReturns,
|
|
90
|
+
relationLoads: options.relationLoads,
|
|
91
|
+
rlsProcedures: options.rlsProcedures,
|
|
92
|
+
schema: toAdvisorSchema(options.schema),
|
|
93
|
+
secretLiterals: options.secretLiterals,
|
|
94
|
+
shapes: options.shapes === void 0 ? void 0 : toAdvisorShapes(options.shapes),
|
|
95
|
+
softDeleteReads: options.softDeleteReads,
|
|
96
|
+
sqlInterpolations: options.sqlInterpolations,
|
|
97
|
+
storageKeyAccesses: options.storageKeyAccesses,
|
|
98
|
+
storageUploads: options.storageUploads,
|
|
99
|
+
vectorNamespaceAccesses: options.vectorNamespaceAccesses,
|
|
100
|
+
workflowCalls: options.workflowCalls,
|
|
101
|
+
workflows: options.workflows,
|
|
102
|
+
wranglerVariables: options.wranglerVariables
|
|
103
|
+
},
|
|
104
|
+
{ source: "static" }
|
|
105
|
+
);
|
|
106
|
+
const formatAdvisories = (findings) => {
|
|
107
|
+
if (findings.length === 0) {
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
const header = `@lunora/codegen: ${String(findings.length)} schema advisor finding${findings.length === 1 ? "" : "s"}`;
|
|
111
|
+
const lines = findings.map((finding) => ` [${finding.level}] ${finding.name}: ${finding.detail}`);
|
|
112
|
+
return [header, ...lines].join("\n");
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export { formatAdvisories, lintSchema };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { Node } from 'ts-morph';
|
|
2
3
|
|
|
3
4
|
const FIELD_NAME_RE = /^[A-Za-z_$][\w$]*$/u;
|
|
@@ -53,13 +54,22 @@ const parseObjectShape = (object) => {
|
|
|
53
54
|
}
|
|
54
55
|
const fieldName = property.getName();
|
|
55
56
|
if (!FIELD_NAME_RE.test(fieldName)) {
|
|
56
|
-
throw new
|
|
57
|
+
throw new LunoraError("INTERNAL", `@lunora/codegen: field name is not a valid JS identifier: ${JSON.stringify(fieldName)}`);
|
|
57
58
|
}
|
|
58
59
|
out[fieldName] = parseValidator(initializer);
|
|
59
60
|
}
|
|
60
61
|
return out;
|
|
61
62
|
};
|
|
62
63
|
const parseArgument = (argument, fallback) => argument && Node.isExpression(argument) ? parseValidator(argument) : fallback;
|
|
64
|
+
const renderLiteralSource = (node) => {
|
|
65
|
+
if (node === void 0) {
|
|
66
|
+
return "undefined";
|
|
67
|
+
}
|
|
68
|
+
if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
|
|
69
|
+
return JSON.stringify(node.getLiteralValue());
|
|
70
|
+
}
|
|
71
|
+
return node.getText();
|
|
72
|
+
};
|
|
63
73
|
const parseBuilderMember = (member, args) => {
|
|
64
74
|
if (SCALAR_KINDS.has(member)) {
|
|
65
75
|
return { kind: member };
|
|
@@ -78,9 +88,10 @@ const parseBuilderMember = (member, args) => {
|
|
|
78
88
|
case "literal": {
|
|
79
89
|
return {
|
|
80
90
|
kind: "literal",
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
91
|
+
// Canonical source text — strings/templates are re-encoded via
|
|
92
|
+
// JSON.stringify (see renderLiteralSource) so escapes/backticks
|
|
93
|
+
// survive; numbers/booleans/null keep their verbatim text.
|
|
94
|
+
literalValue: renderLiteralSource(first)
|
|
84
95
|
};
|
|
85
96
|
}
|
|
86
97
|
case "object": {
|
|
@@ -106,7 +117,7 @@ const parseBuilderMember = (member, args) => {
|
|
|
106
117
|
};
|
|
107
118
|
}
|
|
108
119
|
default: {
|
|
109
|
-
throw new
|
|
120
|
+
throw new LunoraError("INTERNAL", `Unsupported validator kind: ${member}`);
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
};
|
|
@@ -124,7 +135,8 @@ const parseValidatorCall = (call) => {
|
|
|
124
135
|
}
|
|
125
136
|
if (TRANSPARENT_MODIFIERS.has(member)) {
|
|
126
137
|
const receiver = callee.getExpression();
|
|
127
|
-
|
|
138
|
+
const base = Node.isExpression(receiver) ? parseValidator(receiver) : { kind: "any" };
|
|
139
|
+
return member === "check" ? { ...base, hasRefinement: true } : base;
|
|
128
140
|
}
|
|
129
141
|
return parseBuilderMember(member, args);
|
|
130
142
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const HIGH_ENTROPY_TOKEN_RE = /[\w+/=-]{40,}/u;
|
|
2
|
+
const LOWER_RE = /[a-z]/u;
|
|
3
|
+
const UPPER_RE = /[A-Z]/u;
|
|
4
|
+
const DIGIT_RE = /\d/u;
|
|
5
|
+
const HEX_SECRET_RE = /\b(?:[\da-f]{32,}|[\dA-F]{32,})\b/u;
|
|
6
|
+
const isHighEntropy = (value) => {
|
|
7
|
+
const token = HIGH_ENTROPY_TOKEN_RE.exec(value)?.[0];
|
|
8
|
+
if (token === void 0) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return LOWER_RE.test(token) && UPPER_RE.test(token) && DIGIT_RE.test(token);
|
|
12
|
+
};
|
|
13
|
+
const isHexSecret = (value) => HEX_SECRET_RE.test(value);
|
|
14
|
+
const STRIPE_LIVE_KEY_RE = /\b(?:sk|rk)_live_[\dA-Za-z]{20,}/u;
|
|
15
|
+
const AWS_ACCESS_KEY_RE = /\bAKIA[\dA-Z]{16}\b/u;
|
|
16
|
+
const GITHUB_TOKEN_RE = /\bgh[posru]_[\dA-Za-z]{36,}/u;
|
|
17
|
+
const OPENAI_KEY_RE = /\bsk-[\dA-Za-z]{32,}/u;
|
|
18
|
+
const SLACK_TOKEN_RE = /\bxox[abprs]-[\dA-Za-z-]{10,}/u;
|
|
19
|
+
const PRIVATE_KEY_RE = /-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----/u;
|
|
20
|
+
const SECRET_RULES = [
|
|
21
|
+
{ kind: "stripe_live_key", test: (value) => STRIPE_LIVE_KEY_RE.test(value) },
|
|
22
|
+
{ kind: "aws_access_key", test: (value) => AWS_ACCESS_KEY_RE.test(value) },
|
|
23
|
+
{ kind: "github_token", test: (value) => GITHUB_TOKEN_RE.test(value) },
|
|
24
|
+
{ kind: "openai_key", test: (value) => OPENAI_KEY_RE.test(value) },
|
|
25
|
+
{ kind: "slack_token", test: (value) => SLACK_TOKEN_RE.test(value) },
|
|
26
|
+
{ kind: "private_key", test: (value) => PRIVATE_KEY_RE.test(value) },
|
|
27
|
+
{ kind: "high_entropy", test: isHighEntropy },
|
|
28
|
+
{ kind: "hex_secret", test: isHexSecret }
|
|
29
|
+
];
|
|
30
|
+
const secretKindOf = (value) => SECRET_RULES.find((rule) => rule.test(value))?.kind;
|
|
31
|
+
const redact = (value) => `${value.slice(0, 4)}…(${String(value.length)} chars)`;
|
|
32
|
+
|
|
33
|
+
export { SECRET_RULES, redact, secretKindOf };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/codegen",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.50",
|
|
4
4
|
"description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "packages/codegen"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"dist",
|
|
28
|
+
"./dist",
|
|
29
29
|
"__assets__",
|
|
30
30
|
"README.md",
|
|
31
31
|
"LICENSE.md"
|
|
@@ -46,11 +46,14 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/advisor": "1.0.0-alpha.
|
|
50
|
-
"@lunora/
|
|
51
|
-
"@lunora/
|
|
52
|
-
"@lunora/
|
|
53
|
-
"@lunora/
|
|
49
|
+
"@lunora/advisor": "1.0.0-alpha.32",
|
|
50
|
+
"@lunora/agent": "1.0.0-alpha.7",
|
|
51
|
+
"@lunora/container": "1.0.0-alpha.13",
|
|
52
|
+
"@lunora/errors": "1.0.0-alpha.6",
|
|
53
|
+
"@lunora/queue": "1.0.0-alpha.9",
|
|
54
|
+
"@lunora/scheduler": "1.0.0-alpha.11",
|
|
55
|
+
"@lunora/values": "1.0.0-alpha.9",
|
|
56
|
+
"@lunora/workflow": "1.0.0-alpha.11",
|
|
54
57
|
"ts-morph": "^28.0.0"
|
|
55
58
|
},
|
|
56
59
|
"engines": {
|