@supacloud/compiler 0.49.0 → 0.50.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/dist/cli.js CHANGED
@@ -270,6 +270,21 @@ function validateFeatureSpec(spec, module) {
270
270
  if (spec.tenantField !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.tenantField)) {
271
271
  error("feature-tenant-boundary-invalid", `Feature ${spec.name} declares an unsafe tenant field.`);
272
272
  }
273
+ const databaseTransition = spec.transitions.some((transition) => transition.persistence?.boundary === "database");
274
+ if (databaseTransition && spec.database === undefined && spec.file !== undefined) {
275
+ error("feature-database-source-required", `Database Feature ${spec.name} must declare its database source, table and rowType.`);
276
+ }
277
+ if (spec.database !== undefined) {
278
+ if (spec.database.source.startsWith("/") || spec.database.source.includes("\\") || spec.database.source.split("/").includes("..") || !/^[A-Za-z0-9._/-]+$/.test(spec.database.source)) {
279
+ error("feature-database-source-invalid", `Feature ${spec.name} declares an unsafe database source.`);
280
+ }
281
+ if (!/^[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*$/.test(spec.database.table)) {
282
+ error("feature-database-table-invalid", `Feature ${spec.name} must use a schema-qualified database table.`);
283
+ }
284
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(spec.database.rowType)) {
285
+ error("feature-database-row-type-invalid", `Feature ${spec.name} declares an unsafe database row type.`);
286
+ }
287
+ }
273
288
  const names = new Set;
274
289
  for (const transition of spec.transitions) {
275
290
  if (!transition.name.trim() || names.has(transition.name)) {
@@ -406,6 +421,7 @@ function generateFeatureSource(spec) {
406
421
  const sourceSpec = {
407
422
  name: spec.name,
408
423
  states: spec.states,
424
+ ...spec.database === undefined ? {} : { database: spec.database },
409
425
  tenantField: spec.tenantField ?? "tenant_id",
410
426
  transitions: Object.fromEntries(transitions.map((transition) => {
411
427
  const { name, ...options } = transition;
@@ -1434,6 +1450,10 @@ var init_validate = __esm(() => {
1434
1450
  "feature-job-mode": { code: "SC6119", docsUrl: "https://supacloud.dev/errors/SC6119" },
1435
1451
  "feature-job-contract": { code: "SC6120", docsUrl: "https://supacloud.dev/errors/SC6120" },
1436
1452
  "feature-job-context": { code: "SC6121", docsUrl: "https://supacloud.dev/errors/SC6121" },
1453
+ "feature-database-source-required": { code: "SC6122", docsUrl: "https://supacloud.dev/errors/SC6122" },
1454
+ "feature-database-source-invalid": { code: "SC6123", docsUrl: "https://supacloud.dev/errors/SC6123" },
1455
+ "feature-database-table-invalid": { code: "SC6124", docsUrl: "https://supacloud.dev/errors/SC6124" },
1456
+ "feature-database-row-type-invalid": { code: "SC6125", docsUrl: "https://supacloud.dev/errors/SC6125" },
1437
1457
  "feature-route-unresolved": { code: "SC6106", docsUrl: "https://supacloud.dev/errors/SC6106" },
1438
1458
  "feature-route-drift": { code: "SC6107", docsUrl: "https://supacloud.dev/errors/SC6107" },
1439
1459
  "invalid-feature-spec": { code: "SC6108", docsUrl: "https://supacloud.dev/errors/SC6108" }
@@ -1514,6 +1534,7 @@ function buildContractManifest(graph, artifacts) {
1514
1534
  features.push({
1515
1535
  name: module.featureSpec.name,
1516
1536
  states: [...module.featureSpec.states],
1537
+ ...module.featureSpec.database === undefined ? {} : { database: module.featureSpec.database },
1517
1538
  ...module.featureSpec.tenantField === undefined ? {} : { tenantField: module.featureSpec.tenantField },
1518
1539
  transitions: module.featureSpec.transitions.map((transition) => {
1519
1540
  const job = transition.job === undefined ? undefined : (module.jobs ?? []).find((candidate) => candidate.className === transition.job || candidate.name === transition.job);
@@ -19613,6 +19634,10 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
19613
19634
  if (!ts7.isObjectLiteralExpression(input))
19614
19635
  return invalid();
19615
19636
  const name = stringLiteralProp(input, "name");
19637
+ const databaseExpr = getProp(input, "database");
19638
+ const database = databaseExpr && ts7.isObjectLiteralExpression(databaseExpr) ? parseFeatureDatabaseSource(databaseExpr) : undefined;
19639
+ if (databaseExpr !== undefined && database === undefined)
19640
+ return invalid();
19616
19641
  const tenantField = stringLiteralProp(input, "tenantField");
19617
19642
  const statesExpr = getProp(input, "states");
19618
19643
  const transitionObject = getProp(input, "transitions");
@@ -19672,12 +19697,29 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
19672
19697
  return {
19673
19698
  name,
19674
19699
  states,
19700
+ ...database === undefined ? {} : { database },
19675
19701
  ...tenantField === undefined ? {} : { tenantField },
19676
19702
  transitions,
19677
19703
  file: sourcePath(ctx.rootDir, input.getSourceFile().fileName),
19678
19704
  line: lineOf(input)
19679
19705
  };
19680
19706
  }
19707
+ function parseFeatureDatabaseSource(input) {
19708
+ if (input.properties.some((property) => !ts7.isPropertyAssignment(property)))
19709
+ return;
19710
+ const source = stringLiteralProp(input, "source");
19711
+ const table = stringLiteralProp(input, "table");
19712
+ const rowType = stringLiteralProp(input, "rowType");
19713
+ if (!source || !table || !rowType)
19714
+ return;
19715
+ if (source.startsWith("/") || source.includes("\\") || source.split("/").includes("..") || !/^[A-Za-z0-9._/-]+$/.test(source))
19716
+ return;
19717
+ if (!/^[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*$/.test(table))
19718
+ return;
19719
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(rowType))
19720
+ return;
19721
+ return { source, table, rowType };
19722
+ }
19681
19723
  function parseFeaturePersistence(input) {
19682
19724
  if (input.properties.some((property) => !ts7.isPropertyAssignment(property)))
19683
19725
  return;
@@ -24951,10 +24993,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
24951
24993
  import { resolve as resolve18 } from "node:path";
24952
24994
 
24953
24995
  // src/migration-app-version.ts
24954
- var MIGRATION_APP_VERSION = "0.30.0";
24996
+ var MIGRATION_APP_VERSION = "0.31.0";
24955
24997
 
24956
24998
  // src/migration-elysia-version.ts
24957
- var MIGRATION_ELYSIA_VERSION = "0.35.0";
24999
+ var MIGRATION_ELYSIA_VERSION = "0.35.1";
24958
25000
 
24959
25001
  // src/migration-policy.ts
24960
25002
  var cachedCompilerVersion;
@@ -1,4 +1,4 @@
1
- import type { ApplicationGraph, FeaturePersistenceNode } from "./types";
1
+ import type { ApplicationGraph, FeatureDatabaseSourceNode, FeaturePersistenceNode } from "./types";
2
2
  export interface ContractManifest {
3
3
  version: 1;
4
4
  commands: Array<{
@@ -41,6 +41,7 @@ export interface ContractManifest {
41
41
  features: Array<{
42
42
  name: string;
43
43
  states: string[];
44
+ database?: FeatureDatabaseSourceNode;
44
45
  tenantField?: string;
45
46
  transitions: Array<{
46
47
  name: string;
package/dist/index.d.ts CHANGED
@@ -52,7 +52,7 @@ export { DEFAULT_SUPACLOUD_CONFIG, compileOptionsFromConfig, defineSupacloudConf
52
52
  export type { SupaCloudConfig } from "./config";
53
53
  export { camelName } from "./util";
54
54
  export { ANGULAR_ENTERPRISE_RULES, CLEAN_ARCHITECTURE_RULES, MODULAR_MONOLITH_RULES, MODULE_BOUNDARY_PROFILES, getModuleBoundaryPreset, getModuleBoundaryProfile, resolveModuleBoundaries, } from "./profiles";
55
- export type { ApplicationGraph, AspectRefNode, CachedModuleEntry, CheckProjectResult, CommandExecutionCapabilities, CommandNode, CompileOptions, DatabaseGovernanceOptions, CompileResult, CompileStats, ControllerNode, DependencyGraphCache, DependencyGraphIndex, Diagnostic, DiagnosticFix, EffectCompilerOptions, EffectErrorMappingNode, EffectRetryPolicy, EffectRouteNode, ModuleBoundaryPresetName, ModuleBoundaryProfile, ModuleBoundaryRule, ModuleNode, JobNode, ProviderKind, ProviderNode, QueryNode, RouteNode, Scope, TokenKind, TypeSafetyOptions, ValidateOptions, WatchEvent, WatchHandle, WatchOptions, FeatureSpecNode, FeatureTransitionNode, FeaturePersistenceNode, GraphqlOptions, GraphqlContractSummary, OpenApiOptions, OpenApiSecurityScheme, OpenApiServer, } from "./types";
55
+ export type { ApplicationGraph, AspectRefNode, CachedModuleEntry, CheckProjectResult, CommandExecutionCapabilities, CommandNode, CompileOptions, DatabaseGovernanceOptions, CompileResult, CompileStats, ControllerNode, DependencyGraphCache, DependencyGraphIndex, Diagnostic, DiagnosticFix, EffectCompilerOptions, EffectErrorMappingNode, EffectRetryPolicy, EffectRouteNode, ModuleBoundaryPresetName, ModuleBoundaryProfile, ModuleBoundaryRule, ModuleNode, JobNode, ProviderKind, ProviderNode, QueryNode, RouteNode, Scope, TokenKind, TypeSafetyOptions, ValidateOptions, WatchEvent, WatchHandle, WatchOptions, FeatureSpecNode, FeatureTransitionNode, FeatureDatabaseSourceNode, FeaturePersistenceNode, GraphqlOptions, GraphqlContractSummary, OpenApiOptions, OpenApiSecurityScheme, OpenApiServer, } from "./types";
56
56
  export { inspectRouteContracts, validateRouteContracts } from "./route-contracts";
57
57
  export { validateEffectPolicies, validateJobPolicies } from "./effect-governance";
58
58
  export { pullGraphqlSchema } from "./graphql-schema";
package/dist/index.js CHANGED
@@ -269,6 +269,21 @@ function validateFeatureSpec(spec, module) {
269
269
  if (spec.tenantField !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.tenantField)) {
270
270
  error("feature-tenant-boundary-invalid", `Feature ${spec.name} declares an unsafe tenant field.`);
271
271
  }
272
+ const databaseTransition = spec.transitions.some((transition) => transition.persistence?.boundary === "database");
273
+ if (databaseTransition && spec.database === undefined && spec.file !== undefined) {
274
+ error("feature-database-source-required", `Database Feature ${spec.name} must declare its database source, table and rowType.`);
275
+ }
276
+ if (spec.database !== undefined) {
277
+ if (spec.database.source.startsWith("/") || spec.database.source.includes("\\") || spec.database.source.split("/").includes("..") || !/^[A-Za-z0-9._/-]+$/.test(spec.database.source)) {
278
+ error("feature-database-source-invalid", `Feature ${spec.name} declares an unsafe database source.`);
279
+ }
280
+ if (!/^[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*$/.test(spec.database.table)) {
281
+ error("feature-database-table-invalid", `Feature ${spec.name} must use a schema-qualified database table.`);
282
+ }
283
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(spec.database.rowType)) {
284
+ error("feature-database-row-type-invalid", `Feature ${spec.name} declares an unsafe database row type.`);
285
+ }
286
+ }
272
287
  const names = new Set;
273
288
  for (const transition of spec.transitions) {
274
289
  if (!transition.name.trim() || names.has(transition.name)) {
@@ -405,6 +420,7 @@ function generateFeatureSource(spec) {
405
420
  const sourceSpec = {
406
421
  name: spec.name,
407
422
  states: spec.states,
423
+ ...spec.database === undefined ? {} : { database: spec.database },
408
424
  tenantField: spec.tenantField ?? "tenant_id",
409
425
  transitions: Object.fromEntries(transitions.map((transition) => {
410
426
  const { name, ...options } = transition;
@@ -1433,6 +1449,10 @@ var init_validate = __esm(() => {
1433
1449
  "feature-job-mode": { code: "SC6119", docsUrl: "https://supacloud.dev/errors/SC6119" },
1434
1450
  "feature-job-contract": { code: "SC6120", docsUrl: "https://supacloud.dev/errors/SC6120" },
1435
1451
  "feature-job-context": { code: "SC6121", docsUrl: "https://supacloud.dev/errors/SC6121" },
1452
+ "feature-database-source-required": { code: "SC6122", docsUrl: "https://supacloud.dev/errors/SC6122" },
1453
+ "feature-database-source-invalid": { code: "SC6123", docsUrl: "https://supacloud.dev/errors/SC6123" },
1454
+ "feature-database-table-invalid": { code: "SC6124", docsUrl: "https://supacloud.dev/errors/SC6124" },
1455
+ "feature-database-row-type-invalid": { code: "SC6125", docsUrl: "https://supacloud.dev/errors/SC6125" },
1436
1456
  "feature-route-unresolved": { code: "SC6106", docsUrl: "https://supacloud.dev/errors/SC6106" },
1437
1457
  "feature-route-drift": { code: "SC6107", docsUrl: "https://supacloud.dev/errors/SC6107" },
1438
1458
  "invalid-feature-spec": { code: "SC6108", docsUrl: "https://supacloud.dev/errors/SC6108" }
@@ -1513,6 +1533,7 @@ function buildContractManifest(graph, artifacts) {
1513
1533
  features.push({
1514
1534
  name: module.featureSpec.name,
1515
1535
  states: [...module.featureSpec.states],
1536
+ ...module.featureSpec.database === undefined ? {} : { database: module.featureSpec.database },
1516
1537
  ...module.featureSpec.tenantField === undefined ? {} : { tenantField: module.featureSpec.tenantField },
1517
1538
  transitions: module.featureSpec.transitions.map((transition) => {
1518
1539
  const job = transition.job === undefined ? undefined : (module.jobs ?? []).find((candidate) => candidate.className === transition.job || candidate.name === transition.job);
@@ -19572,6 +19593,10 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
19572
19593
  if (!ts7.isObjectLiteralExpression(input))
19573
19594
  return invalid();
19574
19595
  const name = stringLiteralProp(input, "name");
19596
+ const databaseExpr = getProp(input, "database");
19597
+ const database = databaseExpr && ts7.isObjectLiteralExpression(databaseExpr) ? parseFeatureDatabaseSource(databaseExpr) : undefined;
19598
+ if (databaseExpr !== undefined && database === undefined)
19599
+ return invalid();
19575
19600
  const tenantField = stringLiteralProp(input, "tenantField");
19576
19601
  const statesExpr = getProp(input, "states");
19577
19602
  const transitionObject = getProp(input, "transitions");
@@ -19631,12 +19656,29 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
19631
19656
  return {
19632
19657
  name,
19633
19658
  states,
19659
+ ...database === undefined ? {} : { database },
19634
19660
  ...tenantField === undefined ? {} : { tenantField },
19635
19661
  transitions,
19636
19662
  file: sourcePath(ctx.rootDir, input.getSourceFile().fileName),
19637
19663
  line: lineOf(input)
19638
19664
  };
19639
19665
  }
19666
+ function parseFeatureDatabaseSource(input) {
19667
+ if (input.properties.some((property) => !ts7.isPropertyAssignment(property)))
19668
+ return;
19669
+ const source = stringLiteralProp(input, "source");
19670
+ const table = stringLiteralProp(input, "table");
19671
+ const rowType = stringLiteralProp(input, "rowType");
19672
+ if (!source || !table || !rowType)
19673
+ return;
19674
+ if (source.startsWith("/") || source.includes("\\") || source.split("/").includes("..") || !/^[A-Za-z0-9._/-]+$/.test(source))
19675
+ return;
19676
+ if (!/^[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*$/.test(table))
19677
+ return;
19678
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(rowType))
19679
+ return;
19680
+ return { source, table, rowType };
19681
+ }
19640
19682
  function parseFeaturePersistence(input) {
19641
19683
  if (input.properties.some((property) => !ts7.isPropertyAssignment(property)))
19642
19684
  return;
@@ -24633,10 +24675,10 @@ import { readFileSync as readFileSync4 } from "node:fs";
24633
24675
  import { resolve as resolve17 } from "node:path";
24634
24676
 
24635
24677
  // src/migration-app-version.ts
24636
- var MIGRATION_APP_VERSION = "0.30.0";
24678
+ var MIGRATION_APP_VERSION = "0.31.0";
24637
24679
 
24638
24680
  // src/migration-elysia-version.ts
24639
- var MIGRATION_ELYSIA_VERSION = "0.35.0";
24681
+ var MIGRATION_ELYSIA_VERSION = "0.35.1";
24640
24682
 
24641
24683
  // src/migration-policy.ts
24642
24684
  var cachedCompilerVersion;
@@ -1 +1 @@
1
- export declare const MIGRATION_APP_VERSION = "0.30.0";
1
+ export declare const MIGRATION_APP_VERSION = "0.31.0";
@@ -1 +1 @@
1
- export declare const MIGRATION_ELYSIA_VERSION = "0.35.0";
1
+ export declare const MIGRATION_ELYSIA_VERSION = "0.35.1";
package/dist/types.d.ts CHANGED
@@ -372,9 +372,15 @@ export interface FeaturePersistenceNode {
372
372
  receipt: "required" | "none";
373
373
  reconciliation: "required" | "none";
374
374
  }
375
+ export interface FeatureDatabaseSourceNode {
376
+ source: string;
377
+ table: string;
378
+ rowType: string;
379
+ }
375
380
  export interface FeatureSpecNode {
376
381
  name: string;
377
382
  states: string[];
383
+ database?: FeatureDatabaseSourceNode;
378
384
  tenantField?: string;
379
385
  transitions: FeatureTransitionNode[];
380
386
  file?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/compiler",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -73,7 +73,7 @@
73
73
  "@whatwg-node/promise-helpers": "2.0.0"
74
74
  },
75
75
  "devDependencies": {
76
- "@supabase/supabase-js": "2.117.3",
76
+ "@supabase/supabase-js": "2.118.0",
77
77
  "drizzle-orm": "1.0.0-rc.5-5935859",
78
78
  "@types/bun": "^1.4.3",
79
79
  "typescript": "^7.0.2"