flowdular 0.4.3 → 0.6.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.
@@ -18,6 +18,9 @@ var RegistryError = class extends Error {
18
18
  var ALLOWED = Object.freeze({ allowed: true });
19
19
  var NO_POLICIES = Object.freeze([]);
20
20
 
21
+ // ../kernel/src/lifecycle.ts
22
+ var ALLOWED2 = Object.freeze({ allowed: true });
23
+
21
24
  // ../kernel/src/version-range.ts
22
25
  var VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
23
26
  var COMPARATOR = /^(\^|~|>=|<=|>|<|=)?\s*(.+)$/;
@@ -1424,7 +1427,7 @@ var project_schema_default = {
1424
1427
  },
1425
1428
  $defs: {
1426
1429
  deliveryTarget: {
1427
- enum: ["workspace", "git-pr", "official-modules"]
1430
+ enum: ["workspace", "git-pr"]
1428
1431
  },
1429
1432
  gitName: {
1430
1433
  type: "string",
@@ -1587,14 +1590,6 @@ var moduleCatalogSchema = module_catalog_schema_default;
1587
1590
  var moduleArtifactSchema = module_artifact_schema_default;
1588
1591
 
1589
1592
  // ../contracts/src/translations.ts
1590
- var PLURAL_CATEGORIES = [
1591
- "zero",
1592
- "one",
1593
- "two",
1594
- "few",
1595
- "many",
1596
- "other"
1597
- ];
1598
1593
  var WHOLE_NUMBER_SAMPLES = [
1599
1594
  ...Array.from({ length: 200 }, (_, index) => index),
1600
1595
  1e3,
@@ -1605,61 +1600,6 @@ var WHOLE_NUMBER_SAMPLES = [
1605
1600
  1e8,
1606
1601
  1e9
1607
1602
  ];
1608
- function requiredPluralCategories(locale) {
1609
- const rules = new Intl.PluralRules(locale);
1610
- const selected = /* @__PURE__ */ new Set(["other"]);
1611
- for (const sample of WHOLE_NUMBER_SAMPLES) selected.add(rules.select(sample));
1612
- return PLURAL_CATEGORIES.filter((category) => selected.has(category));
1613
- }
1614
- function pluralMember(bundle, key) {
1615
- const dot = key.lastIndexOf(".");
1616
- if (dot <= 0) return null;
1617
- const category = key.slice(dot + 1);
1618
- if (!PLURAL_CATEGORIES.includes(category)) return null;
1619
- const base = key.slice(0, dot);
1620
- if (!Object.hasOwn(bundle, base + ".other")) return null;
1621
- const inflected = PLURAL_CATEGORIES.some(
1622
- (other) => other !== "other" && Object.hasOwn(bundle, base + "." + other)
1623
- );
1624
- return inflected ? { base, category } : null;
1625
- }
1626
- function translationKeys(bundle) {
1627
- const keys = /* @__PURE__ */ new Set();
1628
- for (const key of Object.keys(bundle)) {
1629
- keys.add(pluralMember(bundle, key)?.base ?? key);
1630
- }
1631
- return [...keys].sort();
1632
- }
1633
- function pluralFamilyIssues(bundle, locale) {
1634
- const families = /* @__PURE__ */ new Map();
1635
- for (const key of Object.keys(bundle)) {
1636
- const member = pluralMember(bundle, key);
1637
- if (member === null) continue;
1638
- const categories = families.get(member.base) ?? /* @__PURE__ */ new Set();
1639
- categories.add(member.category);
1640
- families.set(member.base, categories);
1641
- }
1642
- const required = requiredPluralCategories(locale);
1643
- const known = new Intl.PluralRules(
1644
- locale
1645
- ).resolvedOptions().pluralCategories;
1646
- const issues = [];
1647
- for (const base of [...families.keys()].sort()) {
1648
- const categories = families.get(base);
1649
- const missing = required.filter((category) => !categories.has(category));
1650
- if (missing.length > 0) {
1651
- issues.push(
1652
- `${base} lacks ${missing.map((category) => base + "." + category).join(", ")} for ${locale}`
1653
- );
1654
- }
1655
- for (const category of categories) {
1656
- if (!known.includes(category)) {
1657
- issues.push(`${base}.${category} is never selected for ${locale}`);
1658
- }
1659
- }
1660
- }
1661
- return issues;
1662
- }
1663
1603
 
1664
1604
  // ../contracts/src/web.ts
1665
1605
  var RESERVED_WEB_SEGMENTS = Object.freeze([
@@ -1682,8 +1622,55 @@ var WEB_PAGE_PATH = new RegExp(
1682
1622
  `^(?:/|(?:/(?:${SEGMENT}|:[a-z][a-zA-Z0-9]*))+)$`
1683
1623
  );
1684
1624
 
1625
+ // ../contracts/src/branding.ts
1626
+ var DEFAULT_APPLICATION_BRANDING = Object.freeze({
1627
+ appName: "Flowdular",
1628
+ documentTitle: "",
1629
+ description: "",
1630
+ ogImageUrl: "/og.png",
1631
+ faviconUrl: "/favicon.svg",
1632
+ themeColor: "#141B2E",
1633
+ logoUrl: ""
1634
+ });
1635
+ var BRANDING_NAME_MAX = 64;
1636
+ var BRANDING_TITLE_MAX = 120;
1637
+ var BRANDING_DESCRIPTION_MAX = 200;
1638
+ var BRANDING_URL_MAX = 512;
1639
+ var TEXT_BODY = `[^<>"'\\\\\\p{Cc}]`;
1640
+ var BRANDING_NAME_PATTERN = `${TEXT_BODY}{1,${BRANDING_NAME_MAX}}`;
1641
+ var BRANDING_TITLE_PATTERN = `|${TEXT_BODY}{1,${BRANDING_TITLE_MAX}}`;
1642
+ var BRANDING_DESCRIPTION_PATTERN = `|${TEXT_BODY}{1,${BRANDING_DESCRIPTION_MAX}}`;
1643
+ var ASSET_URL = `/(?![/\\\\])[^\\s<>"'\\\\]{0,${BRANDING_URL_MAX - 1}}|https://[^\\s<>"'\\\\/]{1,255}(?:/[^\\s<>"']{0,${BRANDING_URL_MAX - 1}})?`;
1644
+ var BRANDING_ASSET_URL_PATTERN = ASSET_URL;
1645
+ var BRANDING_OPTIONAL_ASSET_URL_PATTERN = `|${ASSET_URL}`;
1646
+ var BRANDING_COLOR_PATTERN = "#[0-9a-fA-F]{6}";
1647
+ var FIELDS = {
1648
+ appName: { pattern: BRANDING_NAME_PATTERN, max: BRANDING_NAME_MAX },
1649
+ documentTitle: {
1650
+ pattern: BRANDING_TITLE_PATTERN,
1651
+ max: BRANDING_TITLE_MAX
1652
+ },
1653
+ description: {
1654
+ pattern: BRANDING_DESCRIPTION_PATTERN,
1655
+ max: BRANDING_DESCRIPTION_MAX
1656
+ },
1657
+ ogImageUrl: { pattern: BRANDING_ASSET_URL_PATTERN, max: BRANDING_URL_MAX },
1658
+ faviconUrl: { pattern: BRANDING_ASSET_URL_PATTERN, max: BRANDING_URL_MAX },
1659
+ themeColor: { pattern: BRANDING_COLOR_PATTERN, max: 7 },
1660
+ logoUrl: {
1661
+ pattern: BRANDING_OPTIONAL_ASSET_URL_PATTERN,
1662
+ max: BRANDING_URL_MAX
1663
+ }
1664
+ };
1665
+ var EXPRESSIONS = new Map(
1666
+ Object.entries(FIELDS).map(([key, field]) => [
1667
+ key,
1668
+ new RegExp(`^(?:${field.pattern})$`, "u")
1669
+ ])
1670
+ );
1671
+
1685
1672
  // ../contracts/src/index.ts
1686
- var PLATFORM_API_VERSION = "0.1.26";
1673
+ var PLATFORM_API_VERSION = "0.2.3";
1687
1674
 
1688
1675
  // ../kernel/src/module-compatibility.ts
1689
1676
  function satisfiesModuleVersion(version, range) {
@@ -1695,18 +1682,42 @@ function compareModuleVersions(left, right) {
1695
1682
  if (!first || !second) return 0;
1696
1683
  return compareVersions(second, first);
1697
1684
  }
1685
+ function assertPlatformLine(moduleId, range, platformVersion) {
1686
+ const [major, minor] = parseVersion(platformVersion);
1687
+ const line = [major, minor, 0];
1688
+ const candidates = [[0, 0, 0]];
1689
+ for (const [named] of range.matchAll(/\d+\.\d+\.\d+/g)) {
1690
+ const version = parseVersion(named);
1691
+ candidates.push(version, [version[0], version[1], version[2] + 1]);
1692
+ }
1693
+ const older = candidates.some(
1694
+ (candidate) => compareVersions(candidate, line) < 0 && rangeSatisfies(candidate.join("."), range)
1695
+ );
1696
+ if (!older) return;
1697
+ throw new RegistryError(
1698
+ "MODULE_PLATFORM_INCOMPATIBLE",
1699
+ `${moduleId} declares platform API ${range}, which also admits versions before ${line.join(".")}; declare "^${line.join(".")}".`
1700
+ );
1701
+ }
1698
1702
  function assertModuleCompatibility(manifest, platformVersion = PLATFORM_API_VERSION) {
1699
1703
  if (parseVersion(manifest.version) === null)
1700
1704
  throw new RegistryError(
1701
1705
  "MODULE_VERSION_INVALID",
1702
1706
  `Invalid version for ${manifest.id}: ${manifest.version}`
1703
1707
  );
1704
- if (manifest.platformApi !== void 0 && (!isValidRange(manifest.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest.platformApi))) {
1708
+ if (manifest.platformApi === void 0)
1709
+ throw new RegistryError(
1710
+ "MODULE_PLATFORM_REQUIRED",
1711
+ `${manifest.id} declares no platformApi; add "platformApi": "^${PLATFORM_API_VERSION}".`
1712
+ );
1713
+ if (!isValidRange(manifest.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest.platformApi)) {
1705
1714
  throw new RegistryError(
1706
1715
  "MODULE_PLATFORM_INCOMPATIBLE",
1707
1716
  `${manifest.id} requires platform API ${manifest.platformApi}; available ${PLATFORM_API_VERSION}.`
1708
1717
  );
1709
1718
  }
1719
+ if (platformVersion !== null)
1720
+ assertPlatformLine(manifest.id, manifest.platformApi, platformVersion);
1710
1721
  const ids = /* @__PURE__ */ new Set();
1711
1722
  for (const dependency of manifest.dependencies) {
1712
1723
  if (!dependency.range.trim() || !isValidRange(dependency.range))
@@ -1876,7 +1887,7 @@ var WEEKDAY_NAMES = "sun mon tue wed thu fri sat".split(" ");
1876
1887
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
1877
1888
  ".git",
1878
1889
  ".flowdular",
1879
- ".coreloom",
1890
+ ".vercel",
1880
1891
  "node_modules",
1881
1892
  "dist"
1882
1893
  ]);
@@ -1990,11 +2001,6 @@ function validateModuleArtifact(value) {
1990
2001
  "Invalid module artifact."
1991
2002
  );
1992
2003
  assertModuleCompatibility(artifact.manifest);
1993
- distributionAssert(
1994
- artifact.manifest.platformApi,
1995
- "MODULE_PLATFORM_REQUIRED",
1996
- "Distributed modules must declare platformApi."
1997
- );
1998
2004
  const paths = /* @__PURE__ */ new Set();
1999
2005
  let size = 0;
2000
2006
  for (const file of artifact.files) {
@@ -2117,7 +2123,6 @@ async function packModule(root, review) {
2117
2123
 
2118
2124
  // src/module-catalog.ts
2119
2125
  import { readFile as readFile4, stat } from "node:fs/promises";
2120
- import { dirname as dirname3, resolve as resolve2 } from "node:path";
2121
2126
 
2122
2127
  // src/workspace.ts
2123
2128
  import { access, readFile as readFile3, realpath } from "node:fs/promises";
@@ -2141,8 +2146,14 @@ async function resolveExistingInside(root, candidate) {
2141
2146
  }
2142
2147
 
2143
2148
  // src/module-catalog.ts
2144
- var OFFICIAL_CATALOG = "https://raw.githubusercontent.com/Flowdular/official-modules/main/registry/index.json";
2145
- var OFFICIAL_PREFIX = "https://raw.githubusercontent.com/Flowdular/official-modules/";
2149
+ function platformAdmits(manifest) {
2150
+ try {
2151
+ assertModuleCompatibility(manifest);
2152
+ return true;
2153
+ } catch {
2154
+ return false;
2155
+ }
2156
+ }
2146
2157
  async function boundedRead(location, limit, fetcher = fetch) {
2147
2158
  if (!location.startsWith("https://")) {
2148
2159
  distributionAssert(
@@ -2158,10 +2169,11 @@ async function boundedRead(location, limit, fetcher = fetch) {
2158
2169
  );
2159
2170
  return result;
2160
2171
  }
2172
+ const url = new URL(location);
2161
2173
  distributionAssert(
2162
- location.startsWith(OFFICIAL_PREFIX),
2174
+ url.username === "" && url.password === "" && !url.search && !url.hash,
2163
2175
  "MODULE_SOURCE_UNTRUSTED",
2164
- "Only the official HTTPS publisher is supported. Use an explicit local catalog for offline releases."
2176
+ "HTTPS module sources must not contain credentials, a query or a fragment."
2165
2177
  );
2166
2178
  const response = await fetcher(location, {
2167
2179
  redirect: "error",
@@ -2170,7 +2182,7 @@ async function boundedRead(location, limit, fetcher = fetch) {
2170
2182
  distributionAssert(
2171
2183
  response.ok && response.body,
2172
2184
  "MODULE_DOWNLOAD_FAILED",
2173
- `Official module download failed (${response.status}).`
2185
+ `Module download failed (${response.status}).`
2174
2186
  );
2175
2187
  const reader = response.body.getReader();
2176
2188
  const chunks = [];
@@ -2192,11 +2204,11 @@ async function boundedRead(location, limit, fetcher = fetch) {
2192
2204
  }
2193
2205
  return Buffer.concat(chunks);
2194
2206
  }
2195
- async function loadModuleCatalog(location = OFFICIAL_CATALOG, fetcher = fetch) {
2207
+ async function loadModuleCatalog(location, fetcher = fetch) {
2196
2208
  distributionAssert(
2197
- location === OFFICIAL_CATALOG || !/^[a-z][a-z0-9+.-]*:/i.test(location),
2209
+ location.startsWith("https://") || !/^[a-z][a-z0-9+.-]*:/i.test(location),
2198
2210
  "MODULE_SOURCE_UNTRUSTED",
2199
- "Use the official catalog or an explicit local catalog path."
2211
+ "Use an explicit HTTPS or local catalog path."
2200
2212
  );
2201
2213
  const catalog = JSON.parse(
2202
2214
  (await boundedRead(location, 4 * 1024 * 1024, fetcher)).toString("utf8")
@@ -2214,11 +2226,6 @@ async function loadModuleCatalog(location = OFFICIAL_CATALOG, fetcher = fetch) {
2214
2226
  "Invalid module release."
2215
2227
  );
2216
2228
  assertModuleCompatibility(release.manifest, null);
2217
- distributionAssert(
2218
- release.manifest.platformApi,
2219
- "MODULE_PLATFORM_REQUIRED",
2220
- "Distributed modules must declare platformApi."
2221
- );
2222
2229
  const identity = release.manifest.id + "@" + release.manifest.version;
2223
2230
  distributionAssert(
2224
2231
  !identities.has(identity),
@@ -2229,26 +2236,6 @@ async function loadModuleCatalog(location = OFFICIAL_CATALOG, fetcher = fetch) {
2229
2236
  }
2230
2237
  return { catalog, location };
2231
2238
  }
2232
- async function releaseLocation(source, release) {
2233
- if (source.location.startsWith("https://")) {
2234
- const prefix = OFFICIAL_PREFIX + release.sourceCommit + "/";
2235
- distributionAssert(
2236
- release.artifact.startsWith(prefix) && new URL(release.artifact).href === release.artifact && !new URL(release.artifact).search && !new URL(release.artifact).hash,
2237
- "MODULE_SOURCE_UNTRUSTED",
2238
- "Release artifact must be pinned to its official source commit."
2239
- );
2240
- return release.artifact;
2241
- }
2242
- distributionAssert(
2243
- !/^[a-z][a-z0-9+.-]*:/i.test(release.artifact),
2244
- "MODULE_SOURCE_UNTRUSTED",
2245
- "An offline catalog must use local artifacts."
2246
- );
2247
- return resolveExistingInside(
2248
- dirname3(resolve2(source.location)),
2249
- release.artifact
2250
- );
2251
- }
2252
2239
  function resolveModuleReleases(catalog, target, installed) {
2253
2240
  const split = target.lastIndexOf("@");
2254
2241
  const id = split < 0 ? target : target.slice(0, split);
@@ -2286,10 +2273,7 @@ function resolveModuleReleases(catalog, target, installed) {
2286
2273
  ];
2287
2274
  }
2288
2275
  function platformCompatible(candidate) {
2289
- return satisfiesModuleVersion(
2290
- PLATFORM_API_VERSION,
2291
- candidate.manifest.platformApi
2292
- );
2276
+ return platformAdmits(candidate.manifest);
2293
2277
  }
2294
2278
  let attempts = 0;
2295
2279
  function search(selected2, pending) {
@@ -2347,31 +2331,24 @@ function resolveModuleReleases(catalog, target, installed) {
2347
2331
  return release ? [release] : [];
2348
2332
  });
2349
2333
  }
2350
- async function readRelease(source, release, fetcher = fetch) {
2351
- return boundedRead(
2352
- await releaseLocation(source, release),
2353
- MAX_ARTIFACT_BYTES,
2354
- fetcher
2355
- );
2356
- }
2357
2334
 
2358
2335
  // src/module-install.ts
2359
2336
  import {
2360
2337
  lstat as lstat3,
2361
2338
  mkdir,
2362
- readFile as readFile8,
2363
- readdir as readdir4,
2339
+ readFile as readFile7,
2340
+ readdir as readdir3,
2364
2341
  rename,
2365
2342
  rm,
2366
2343
  writeFile
2367
2344
  } from "node:fs/promises";
2368
- import { dirname as dirname6, join as join6, relative as relative5 } from "node:path";
2345
+ import { dirname as dirname4, join as join5, relative as relative4 } from "node:path";
2369
2346
 
2370
2347
  // src/sdk.ts
2371
2348
  import { createRequire } from "node:module";
2372
2349
  import { readFile as readFile5 } from "node:fs/promises";
2373
- import { dirname as dirname4, join as join3 } from "node:path";
2374
- var SDK_VERSION = "0.4.3";
2350
+ import { dirname as dirname3, join as join3 } from "node:path";
2351
+ var SDK_VERSION = "0.6.0";
2375
2352
  function npmPackage(specifier) {
2376
2353
  return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
2377
2354
  }
@@ -2395,7 +2372,7 @@ async function sdkModules(workspace) {
2395
2372
  if (!/^@flowdular\/sdk\/modules\/[a-z0-9-]+$/.test(entry.import))
2396
2373
  throw new Error("Invalid SDK module entrypoint.");
2397
2374
  const path = await resolveExistingInside(
2398
- dirname4(indexPath),
2375
+ dirname3(indexPath),
2399
2376
  entry.manifest
2400
2377
  );
2401
2378
  if (result.has(path)) throw new Error("Duplicate SDK module path.");
@@ -2472,10 +2449,184 @@ async function findModuleFiles(workspace) {
2472
2449
  }
2473
2450
 
2474
2451
  // src/module-validate.ts
2475
- import { access as access2, readFile as readFile7, readdir as readdir3 } from "node:fs/promises";
2476
- import { dirname as dirname5, join as join5, relative as relative4 } from "node:path";
2477
2452
  import { parse as parseYaml3 } from "yaml";
2478
2453
 
2454
+ // ../database/src/contracts.ts
2455
+ var DATABASE_ADAPTER_IDS = Object.freeze({
2456
+ postgresql: "flowdular.postgresql"
2457
+ });
2458
+ var DATABASE_DIALECT_IDS = Object.freeze({
2459
+ postgresql: "postgresql"
2460
+ });
2461
+ var DATABASE_CAPABILITY_IDS = Object.freeze({
2462
+ MIGRATION_LOCK: "flowdular.database.migration-lock",
2463
+ RETURNING: "flowdular.database.returning",
2464
+ ROW_LEVEL_SECURITY: "flowdular.database.row-level-security",
2465
+ SCHEMA_INTROSPECTION: "flowdular.database.schema-introspection",
2466
+ TENANT_CONTEXT: "flowdular.database.tenant-context",
2467
+ TRANSACTIONAL_DDL: "flowdular.database.transactional-ddl",
2468
+ TRANSACTIONS: "flowdular.database.transactions"
2469
+ });
2470
+ var DatabaseError = class extends Error {
2471
+ constructor(code, message, options) {
2472
+ super(message, options);
2473
+ this.code = code;
2474
+ this.name = "DatabaseError";
2475
+ }
2476
+ code;
2477
+ };
2478
+ function assertDatabaseId(id, kind) {
2479
+ if (!/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/.test(id)) {
2480
+ throw new DatabaseError(
2481
+ "INVALID_ARGUMENT",
2482
+ `"${id}" is not a valid database ${kind} id.`
2483
+ );
2484
+ }
2485
+ }
2486
+ function assertSchemaName(name) {
2487
+ if (!/^[a-z_][a-z0-9_]{0,62}$/.test(name)) {
2488
+ throw new DatabaseError(
2489
+ "INVALID_ARGUMENT",
2490
+ `"${name}" is not a valid database schema object name.`
2491
+ );
2492
+ }
2493
+ }
2494
+ function databasePlaceholder(dialectId, position) {
2495
+ assertDatabaseId(dialectId, "dialect");
2496
+ if (!Number.isSafeInteger(position) || position < 1) {
2497
+ throw new DatabaseError(
2498
+ "INVALID_ARGUMENT",
2499
+ "Database placeholder positions start at 1."
2500
+ );
2501
+ }
2502
+ if (dialectId === DATABASE_DIALECT_IDS.postgresql) return `$${position}`;
2503
+ throw new DatabaseError(
2504
+ "UNSUPPORTED_CAPABILITY",
2505
+ `The well-known placeholder helper does not support dialect "${dialectId}".`
2506
+ );
2507
+ }
2508
+ function databaseDialectSql(dialectId) {
2509
+ assertDatabaseId(dialectId, "dialect");
2510
+ const ledger = dialectId === DATABASE_DIALECT_IDS.postgresql ? { appliedAt: "BIGINT", suffix: "" } : void 0;
2511
+ if (!ledger) {
2512
+ throw new DatabaseError(
2513
+ "UNSUPPORTED_CAPABILITY",
2514
+ `The well-known dialect SQL helper does not support dialect "${dialectId}".`
2515
+ );
2516
+ }
2517
+ return Object.freeze({
2518
+ placeholder: (position) => databasePlaceholder(dialectId, position),
2519
+ migrationLedgerDdl: (table) => {
2520
+ assertSchemaName(table);
2521
+ return `CREATE TABLE IF NOT EXISTS ${table} (
2522
+ namespace TEXT NOT NULL,
2523
+ id TEXT NOT NULL,
2524
+ dialect TEXT NOT NULL,
2525
+ checksum TEXT NOT NULL,
2526
+ applied_at ${ledger.appliedAt} NOT NULL,
2527
+ PRIMARY KEY (namespace, id)
2528
+ )${ledger.suffix};
2529
+ -- Default application grants must never make the checksum ledger writable.
2530
+ -- Revoke existing grants as well, so databases created before this rule heal.
2531
+ DO $ledger_privileges$
2532
+ DECLARE
2533
+ granted_role RECORD;
2534
+ BEGIN
2535
+ FOR granted_role IN
2536
+ SELECT DISTINCT acl.grantee, roles.rolname
2537
+ FROM pg_class AS relation
2538
+ CROSS JOIN LATERAL aclexplode(relation.relacl) AS acl
2539
+ LEFT JOIN pg_roles AS roles ON roles.oid = acl.grantee
2540
+ WHERE relation.oid = '${table}'::regclass
2541
+ AND acl.grantee <> relation.relowner
2542
+ LOOP
2543
+ EXECUTE format('REVOKE ALL PRIVILEGES ON TABLE %s FROM %s',
2544
+ '${table}'::regclass,
2545
+ CASE WHEN granted_role.grantee = 0 THEN 'PUBLIC'
2546
+ ELSE quote_ident(granted_role.rolname) END);
2547
+ END LOOP;
2548
+ END
2549
+ $ledger_privileges$;`;
2550
+ }
2551
+ });
2552
+ }
2553
+
2554
+ // ../database/src/postgresql.ts
2555
+ var CAPABILITIES = Object.freeze({
2556
+ features: Object.freeze([
2557
+ DATABASE_CAPABILITY_IDS.MIGRATION_LOCK,
2558
+ DATABASE_CAPABILITY_IDS.RETURNING,
2559
+ DATABASE_CAPABILITY_IDS.ROW_LEVEL_SECURITY,
2560
+ DATABASE_CAPABILITY_IDS.SCHEMA_INTROSPECTION,
2561
+ DATABASE_CAPABILITY_IDS.TENANT_CONTEXT,
2562
+ DATABASE_CAPABILITY_IDS.TRANSACTIONAL_DDL,
2563
+ DATABASE_CAPABILITY_IDS.TRANSACTIONS
2564
+ ]),
2565
+ parameterStyle: "numbered",
2566
+ migrationLock: "advisory-transaction",
2567
+ transactionalDdl: true,
2568
+ returning: true,
2569
+ isolationLevels: [
2570
+ "read-committed",
2571
+ "repeatable-read",
2572
+ "serializable"
2573
+ ],
2574
+ cancellation: "before-start",
2575
+ schemaIntrospection: true,
2576
+ tenantIsolation: "transaction-local-rls",
2577
+ rootOperations: "allowed",
2578
+ sql: databaseDialectSql(DATABASE_DIALECT_IDS.postgresql)
2579
+ });
2580
+
2581
+ // ../database/src/provider.ts
2582
+ var PGLITE_RUNTIME_ROLE = "flowdular_runtime";
2583
+ var PGLITE_BACKGROUND_ROLE = "flowdular_background";
2584
+ var PGLITE_BOOTSTRAP = `DO $$
2585
+ BEGIN
2586
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${PGLITE_RUNTIME_ROLE}') THEN
2587
+ CREATE ROLE ${PGLITE_RUNTIME_ROLE} NOSUPERUSER NOBYPASSRLS;
2588
+ END IF;
2589
+ END
2590
+ $$;
2591
+ DO $$
2592
+ BEGIN
2593
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${PGLITE_BACKGROUND_ROLE}') THEN
2594
+ CREATE ROLE ${PGLITE_BACKGROUND_ROLE} NOSUPERUSER NOBYPASSRLS;
2595
+ END IF;
2596
+ END
2597
+ $$;
2598
+ GRANT USAGE ON SCHEMA public TO ${PGLITE_RUNTIME_ROLE}, ${PGLITE_BACKGROUND_ROLE};
2599
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
2600
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${PGLITE_RUNTIME_ROLE};
2601
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
2602
+ GRANT USAGE, SELECT ON SEQUENCES TO ${PGLITE_RUNTIME_ROLE};
2603
+ /* The background role gets no default table grant. A table it may poll across
2604
+ tenants says so itself, in its own migration, by granting the named columns
2605
+ under a FOR SELECT policy. Anything that forgets to is invisible to it. */
2606
+ `;
2607
+
2608
+ // ../database/src/backup.ts
2609
+ var BACKUP_KEY_VARIABLES = Object.freeze([
2610
+ "FD_AGENT_CREDENTIAL_KEY",
2611
+ "FD_AGENT_RUN_GRANT_KEY",
2612
+ "FD_APPROVAL_GRANT_KEY",
2613
+ "FD_WORKFLOWS_PAYLOAD_KEY",
2614
+ "FD_WORKFLOWS_CURSOR_KEY",
2615
+ "FD_AUTH_MFA_KEY",
2616
+ "FD_AUTOMATIONS_CREDENTIAL_KEY",
2617
+ "FD_NOTIFICATIONS_SECRET_KEY",
2618
+ "FD_STORAGE_ENCRYPTION_KEY",
2619
+ "FD_CONNECTORS_SECRET_KEY",
2620
+ "FD_AUDIT_ANCHOR_KEY"
2621
+ ]);
2622
+
2623
+ // ../database/src/reset.ts
2624
+ var TABLE_QUERY = Object.freeze({
2625
+ [DATABASE_DIALECT_IDS.postgresql]: `SELECT tablename AS name FROM pg_tables
2626
+ WHERE schemaname = current_schema()
2627
+ ORDER BY tablename`
2628
+ });
2629
+
2479
2630
  // src/dom-properties.ts
2480
2631
  var DOM_PROPERTY_SPELLINGS = [
2481
2632
  "acceptCharset",
@@ -2493,6 +2644,7 @@ var DOM_PROPERTY_SPELLINGS = [
2493
2644
  "disablePictureInPicture",
2494
2645
  "disableRemotePlayback",
2495
2646
  "encType",
2647
+ "enterKeyHint",
2496
2648
  "fetchPriority",
2497
2649
  "formAction",
2498
2650
  "formEncType",
@@ -2509,6 +2661,7 @@ var DOM_PROPERTY_SPELLINGS = [
2509
2661
  "itemScope",
2510
2662
  "itemType",
2511
2663
  "maxLength",
2664
+ "minLength",
2512
2665
  "noModule",
2513
2666
  "noValidate",
2514
2667
  "playsInline",
@@ -2524,426 +2677,10 @@ var DOM_PROPERTY_SPELLINGS = [
2524
2677
  var SPELLINGS = new Map(
2525
2678
  DOM_PROPERTY_SPELLINGS.map((name) => [name.toLowerCase(), name])
2526
2679
  );
2527
- function hostTagAttributes(source) {
2528
- const tags = [];
2529
- for (const match of source.matchAll(/<[a-z][a-z0-9-]*/g)) {
2530
- const start = match.index + match[0].length;
2531
- let depth = 0;
2532
- let quote = "";
2533
- let text = "";
2534
- let end = start;
2535
- for (; end < source.length; end += 1) {
2536
- const character = source[end];
2537
- if (quote !== "") {
2538
- if (character === quote) quote = "";
2539
- text += " ";
2540
- continue;
2541
- }
2542
- if (depth > 0) {
2543
- if (character === "{") depth += 1;
2544
- else if (character === "}") depth -= 1;
2545
- text += depth === 0 ? character : " ";
2546
- continue;
2547
- }
2548
- if (character === "{") {
2549
- depth = 1;
2550
- text += character;
2551
- continue;
2552
- }
2553
- if (character === '"' || character === "'") {
2554
- quote = character;
2555
- text += character;
2556
- continue;
2557
- }
2558
- if (character === ">") break;
2559
- text += character;
2560
- }
2561
- tags.push({ start, text });
2562
- }
2563
- return tags;
2564
- }
2565
- function domPropertyMisspellings(source) {
2566
- const found = [];
2567
- for (const tag of hostTagAttributes(source)) {
2568
- for (const attribute of tag.text.matchAll(
2569
- /(?<=\s)([A-Za-z][A-Za-z-]*)=/g
2570
- )) {
2571
- const name = attribute[1];
2572
- const expected = SPELLINGS.get(name.toLowerCase());
2573
- if (expected === void 0 || expected === name) continue;
2574
- const index = tag.start + attribute.index;
2575
- found.push({
2576
- name,
2577
- expected,
2578
- index,
2579
- line: source.slice(0, index).split("\n").length
2580
- });
2581
- }
2582
- }
2583
- return found.sort((left, right) => left.index - right.index);
2584
- }
2585
-
2586
- // src/module-validate.ts
2587
- function issue(code, message, path, severity = "error") {
2588
- return { code, message, path, severity };
2589
- }
2590
- async function exists(path) {
2591
- try {
2592
- await access2(path);
2593
- return true;
2594
- } catch {
2595
- return false;
2596
- }
2597
- }
2598
- async function readJson(path) {
2599
- return JSON.parse(await readFile7(path, "utf8"));
2600
- }
2601
- async function clientSourceFiles(moduleRoot) {
2602
- const clientRoot = join5(moduleRoot, "src/client");
2603
- if (!await exists(clientRoot)) return [];
2604
- return (await readdir3(clientRoot, {
2605
- recursive: true,
2606
- withFileTypes: true
2607
- })).filter((entry) => entry.isFile() && /\.(?:ts|tsx|tsrx)$/.test(entry.name)).map((entry) => join5(entry.parentPath, entry.name));
2608
- }
2609
- async function platformIssues(moduleRoot, manifest) {
2610
- const issues = [];
2611
- let exports = {};
2612
- let packageName;
2613
- let packageVersion;
2614
- try {
2615
- const pkg = await readJson(join5(moduleRoot, "package.json"));
2616
- exports = pkg.exports ?? {};
2617
- packageName = pkg.name;
2618
- packageVersion = pkg.version;
2619
- } catch (error) {
2620
- issues.push(
2621
- issue(
2622
- "PACKAGE_MANIFEST_INVALID",
2623
- `package.json could not be read: ${error instanceof Error ? error.message : String(error)}`,
2624
- "package.json"
2625
- )
2626
- );
2627
- return issues;
2628
- }
2629
- if (packageName !== manifest.package) {
2630
- issues.push(
2631
- issue(
2632
- "PACKAGE_NAME_MISMATCH",
2633
- `package.json name "${String(packageName)}" differs from module.json package "${manifest.package}".`,
2634
- "package.json"
2635
- )
2636
- );
2637
- }
2638
- if (manifest.platformApi === void 0) {
2639
- issues.push(
2640
- issue(
2641
- "PLATFORM_API_MISSING",
2642
- `module.json declares no platformApi; add "platformApi": "^${PLATFORM_API_VERSION}" so the platform contract the module was built against is checked.`,
2643
- "module.json"
2644
- )
2645
- );
2646
- }
2647
- if (packageVersion !== manifest.version) {
2648
- issues.push(
2649
- issue(
2650
- "PACKAGE_VERSION_MISMATCH",
2651
- `package.json version "${String(packageVersion)}" differs from module.json version "${manifest.version}".`,
2652
- "package.json"
2653
- )
2654
- );
2655
- }
2656
- if (manifest.platform?.server === true) {
2657
- if (!await exists(join5(moduleRoot, "src/platform.ts"))) {
2658
- issues.push(
2659
- issue(
2660
- "PLATFORM_SERVER_ENTRY_MISSING",
2661
- "platform.server requires src/platform.ts exporting createServerComposition.",
2662
- "src/platform.ts"
2663
- )
2664
- );
2665
- }
2666
- if (!("./platform" in exports)) {
2667
- issues.push(
2668
- issue(
2669
- "PLATFORM_EXPORT_MISSING",
2670
- 'platform.server requires a "./platform" entry in package.json exports.',
2671
- "package.json"
2672
- )
2673
- );
2674
- }
2675
- }
2676
- if (manifest.platform?.client === true) {
2677
- if (!await exists(join5(moduleRoot, "src/client/index.ts"))) {
2678
- issues.push(
2679
- issue(
2680
- "PLATFORM_CLIENT_ENTRY_MISSING",
2681
- "platform.client requires src/client/index.ts exporting createClientContribution.",
2682
- "src/client/index.ts"
2683
- )
2684
- );
2685
- }
2686
- if (!("./client" in exports)) {
2687
- issues.push(
2688
- issue(
2689
- "PLATFORM_CLIENT_EXPORT_MISSING",
2690
- 'platform.client requires a "./client" entry in package.json exports.',
2691
- "package.json"
2692
- )
2693
- );
2694
- }
2695
- }
2696
- return issues;
2697
- }
2698
- async function specIssues(moduleRoot, manifest, specDirectory) {
2699
- const specPath = join5(specDirectory, "module.yaml");
2700
- let spec;
2701
- try {
2702
- spec = parseYaml3(
2703
- await readFile7(join5(moduleRoot, specPath), "utf8")
2704
- );
2705
- } catch (error) {
2706
- return [
2707
- issue(
2708
- "SPEC_FILE_MISSING",
2709
- `Module specification could not be read: ${error instanceof Error ? error.message : String(error)}`,
2710
- specPath,
2711
- "warning"
2712
- )
2713
- ];
2714
- }
2715
- const issues = [];
2716
- if (spec.id !== manifest.id) {
2717
- issues.push(
2718
- issue(
2719
- "SPEC_ID_MISMATCH",
2720
- `Specification id "${String(spec.id)}" differs from module.json id "${manifest.id}".`,
2721
- specPath
2722
- )
2723
- );
2724
- }
2725
- if (String(spec.specVersion) !== manifest.version) {
2726
- issues.push(
2727
- issue(
2728
- "SPEC_VERSION_DRIFT",
2729
- `module.json version ${manifest.version} differs from specVersion ${String(spec.specVersion)}.`,
2730
- specPath,
2731
- "warning"
2732
- )
2733
- );
2734
- }
2735
- if (Array.isArray(spec.dependencies)) {
2736
- const declared = spec.dependencies.map((entry) => `${String(entry.id)}@${String(entry.range)}`);
2737
- const actual = manifest.dependencies.map(
2738
- (entry) => `${entry.id}@${entry.range}`
2739
- );
2740
- const drift = [
2741
- ...declared.filter((entry) => !actual.includes(entry)),
2742
- ...actual.filter((entry) => !declared.includes(entry))
2743
- ];
2744
- if (drift.length > 0) {
2745
- issues.push(
2746
- issue(
2747
- "SPEC_DEPENDENCY_DRIFT",
2748
- `Specification dependencies differ from module.json: ${drift.join(", ")}. Run module version bump or align the ranges by hand.`,
2749
- specPath,
2750
- "warning"
2751
- )
2752
- );
2753
- }
2754
- }
2755
- return issues;
2756
- }
2757
- async function translationIssues(moduleRoot, manifest, projectLocales) {
2758
- const issues = [];
2759
- if (projectLocales) {
2760
- for (const locale of manifest.locales) {
2761
- if (!projectLocales.includes(locale)) {
2762
- issues.push(
2763
- issue(
2764
- "LOCALE_NOT_IN_PROJECT",
2765
- `Locale "${locale}" is not listed in flowdular.json locales.`,
2766
- "module.json",
2767
- "warning"
2768
- )
2769
- );
2770
- }
2771
- }
2772
- }
2773
- if (!manifest.capabilities.includes("translations")) return issues;
2774
- const keySets = /* @__PURE__ */ new Map();
2775
- const referenceBundleKeys = /* @__PURE__ */ new Set();
2776
- for (const locale of manifest.locales) {
2777
- const path = `translations/${locale}.json`;
2778
- if (!await exists(join5(moduleRoot, path))) {
2779
- issues.push(
2780
- issue(
2781
- "TRANSLATION_FILE_MISSING",
2782
- `Locale "${locale}" has no ${path}.`,
2783
- path
2784
- )
2785
- );
2786
- continue;
2787
- }
2788
- try {
2789
- const bundle = await readJson(join5(moduleRoot, path));
2790
- if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
2791
- throw new Error("Expected a JSON object.");
2792
- }
2793
- const strings = bundle;
2794
- keySets.set(locale, translationKeys(strings));
2795
- if (keySets.size === 1) {
2796
- for (const key of Object.keys(strings)) referenceBundleKeys.add(key);
2797
- }
2798
- const plurals = pluralFamilyIssues(strings, locale);
2799
- if (plurals.length > 0) {
2800
- issues.push(
2801
- issue(
2802
- "TRANSLATION_PLURAL_INCOMPLETE",
2803
- `${path} plural families do not match the ${locale} plural rules: ${plurals.join("; ")}.`,
2804
- path
2805
- )
2806
- );
2807
- }
2808
- } catch (error) {
2809
- issues.push(
2810
- issue(
2811
- "TRANSLATION_PARSE_ERROR",
2812
- `${path} is not a JSON object: ${error instanceof Error ? error.message : String(error)}`,
2813
- path
2814
- )
2815
- );
2816
- }
2817
- }
2818
- const [reference, ...others] = [...keySets.entries()];
2819
- if (!reference) return issues;
2820
- for (const [locale, keys] of others) {
2821
- const missing = reference[1].filter((key) => !keys.includes(key));
2822
- const extra = keys.filter((key) => !reference[1].includes(key));
2823
- if (missing.length === 0 && extra.length === 0) continue;
2824
- const detail = [
2825
- ...missing.length > 0 ? [`missing ${missing.join(", ")}`] : [],
2826
- ...extra.length > 0 ? [`extra ${extra.join(", ")}`] : []
2827
- ].join("; ");
2828
- issues.push(
2829
- issue(
2830
- "TRANSLATION_KEYS_MISMATCH",
2831
- `translations/${locale}.json keys differ from translations/${reference[0]}.json: ${detail}.`,
2832
- `translations/${locale}.json`
2833
- )
2834
- );
2835
- }
2836
- const referenceKeys = /* @__PURE__ */ new Set([...reference[1], ...referenceBundleKeys]);
2837
- const files = await clientSourceFiles(moduleRoot);
2838
- if (files.length > 0) {
2839
- const namespace = manifest.id.split(".")[0] ?? manifest.id;
2840
- const prefix = namespace + ".";
2841
- const missing = /* @__PURE__ */ new Map();
2842
- const pattern = /\bt\s*\(\s*(['"])([a-zA-Z0-9._-]+)\1/g;
2843
- for (const file of files) {
2844
- const source = await readFile7(file, "utf8");
2845
- for (const match of source.matchAll(pattern)) {
2846
- const fullKey = match[2];
2847
- if (!fullKey.startsWith(prefix) || fullKey.endsWith(".")) continue;
2848
- const localKey = fullKey.slice(prefix.length);
2849
- if (!referenceKeys.has(localKey)) {
2850
- missing.set(fullKey, relative4(moduleRoot, file));
2851
- }
2852
- }
2853
- }
2854
- for (const [key, path] of [...missing].sort(
2855
- ([left], [right]) => left.localeCompare(right)
2856
- )) {
2857
- issues.push(
2858
- issue(
2859
- "TRANSLATION_KEY_MISSING",
2860
- `Translation key "${key}" is used by the client but absent from translations/${reference[0]}.json.`,
2861
- path
2862
- )
2863
- );
2864
- }
2865
- }
2866
- return issues;
2867
- }
2868
- function filtersTags(source) {
2869
- const tags = [];
2870
- for (const match of source.matchAll(/<Filters\b/g)) {
2871
- let depth = 0;
2872
- let end = match.index + match[0].length;
2873
- for (; end < source.length; end += 1) {
2874
- const character = source[end];
2875
- if (character === "{") depth += 1;
2876
- else if (character === "}") depth -= 1;
2877
- else if (character === ">" && depth === 0) break;
2878
- }
2879
- tags.push(source.slice(match.index, end));
2880
- }
2881
- return tags;
2882
- }
2883
- async function userInterfaceIssues(moduleRoot) {
2884
- const issues = [];
2885
- for (const file of await clientSourceFiles(moduleRoot)) {
2886
- const source = await readFile7(file, "utf8");
2887
- const path = relative4(moduleRoot, file);
2888
- if (/<table\b/.test(source)) {
2889
- issues.push(
2890
- issue(
2891
- "RAW_TABLE_FORBIDDEN",
2892
- "Module screens use Table or TableCard from @flowdular/ui instead of raw table markup.",
2893
- path
2894
- )
2895
- );
2896
- }
2897
- if (/from\s+['"]@octanejs\/tanstack-table['"]/.test(source)) {
2898
- issues.push(
2899
- issue(
2900
- "TANSTACK_TABLE_DIRECT_IMPORT",
2901
- "Modules use the shared Table contract from @flowdular/ui; TanStack configuration belongs to the UI package.",
2902
- path
2903
- )
2904
- );
2905
- }
2906
- const misspelled = domPropertyMisspellings(source);
2907
- if (misspelled.length > 0) {
2908
- issues.push(
2909
- issue(
2910
- "DOM_PROPERTY_CASING",
2911
- "Host elements use Octane's DOM property spelling: " + misspelled.map(
2912
- (found) => `line ${found.line} ${found.name} as ${found.expected}`
2913
- ).join(", ") + ".",
2914
- path
2915
- )
2916
- );
2917
- }
2918
- if (filtersTags(source).some((tag) => !/\slabel=/.test(tag))) {
2919
- issues.push(
2920
- issue(
2921
- "FILTERS_LABEL_MISSING",
2922
- 'Filters from @flowdular/ui needs a translated label; without one it shows the English word "Filters" in every locale.',
2923
- path
2924
- )
2925
- );
2926
- }
2927
- }
2928
- return issues;
2929
- }
2930
- async function moduleLayoutIssues(moduleRoot, manifest, options = {}) {
2931
- return [
2932
- ...await platformIssues(moduleRoot, manifest),
2933
- ...await specIssues(
2934
- moduleRoot,
2935
- manifest,
2936
- options.specDirectory ?? "spec"
2937
- ),
2938
- ...await translationIssues(moduleRoot, manifest, options.projectLocales),
2939
- ...await userInterfaceIssues(moduleRoot)
2940
- ];
2941
- }
2942
2680
 
2943
2681
  // src/module-install.ts
2944
2682
  var LOCK = "flowdular.modules.lock.json";
2945
- var TRANSACTION = ".flowdular-module-install";
2946
- async function exists2(path) {
2683
+ async function exists(path) {
2947
2684
  try {
2948
2685
  await lstat3(path);
2949
2686
  return true;
@@ -2953,14 +2690,14 @@ async function exists2(path) {
2953
2690
  }
2954
2691
  }
2955
2692
  async function safeParents(workspace, path) {
2956
- const parts = relative5(
2693
+ const parts = relative4(
2957
2694
  workspace.root,
2958
2695
  resolveInside(workspace.root, path)
2959
2696
  ).split("/");
2960
2697
  let current = workspace.root;
2961
2698
  for (const part of parts) {
2962
- current = join6(current, part);
2963
- if (!await exists2(current)) break;
2699
+ current = join5(current, part);
2700
+ if (!await exists(current)) break;
2964
2701
  distributionAssert(
2965
2702
  !(await lstat3(current)).isSymbolicLink(),
2966
2703
  "MODULE_DESTINATION_LINK",
@@ -2985,7 +2722,7 @@ function validateLock(workspace, value) {
2985
2722
  );
2986
2723
  const path = resolveInside(workspace.root, entry.directory);
2987
2724
  distributionAssert(
2988
- moduleRoots(workspace).some((root) => dirname6(path) === root) && !ids.has(entry.id) && !dirs.has(path.toLowerCase()),
2725
+ moduleRoots(workspace).some((root) => dirname4(path) === root) && !ids.has(entry.id) && !dirs.has(path.toLowerCase()),
2989
2726
  "MODULE_LOCK_INVALID",
2990
2727
  "Duplicate or invalid installed module path."
2991
2728
  );
@@ -3002,17 +2739,17 @@ function validateLock(workspace, value) {
3002
2739
  return lock;
3003
2740
  }
3004
2741
  async function readLock(workspace) {
3005
- const path = join6(workspace.root, LOCK);
2742
+ const path = join5(workspace.root, LOCK);
3006
2743
  await safeParents(workspace, path);
3007
- if (!await exists2(path))
2744
+ if (!await exists(path))
3008
2745
  return { lock: { schemaVersion: 1, modules: [] }, raw: null };
3009
- const raw = await readFile8(path, "utf8");
2746
+ const raw = await readFile7(path, "utf8");
3010
2747
  return { lock: validateLock(workspace, JSON.parse(raw)), raw };
3011
2748
  }
3012
2749
  async function directoryHashes(root) {
3013
2750
  const hashes = /* @__PURE__ */ Object.create(null);
3014
2751
  async function visit(directory, prefix) {
3015
- for (const entry of await readdir4(directory, { withFileTypes: true })) {
2752
+ for (const entry of await readdir3(directory, { withFileTypes: true })) {
3016
2753
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git")
3017
2754
  continue;
3018
2755
  distributionAssert(
@@ -3022,14 +2759,14 @@ async function directoryHashes(root) {
3022
2759
  );
3023
2760
  const path = prefix + entry.name;
3024
2761
  if (entry.isDirectory())
3025
- await visit(join6(directory, entry.name), path + "/");
2762
+ await visit(join5(directory, entry.name), path + "/");
3026
2763
  else {
3027
2764
  distributionAssert(
3028
2765
  entry.isFile(),
3029
2766
  "MODULE_LOCAL_CHANGES",
3030
2767
  "Installed module contains a special file."
3031
2768
  );
3032
- hashes[path] = hashBytes(await readFile8(join6(directory, entry.name)));
2769
+ hashes[path] = hashBytes(await readFile7(join5(directory, entry.name)));
3033
2770
  }
3034
2771
  }
3035
2772
  }
@@ -3043,7 +2780,7 @@ async function assertUnmodified(workspace, entry) {
3043
2780
  const path = resolveInside(workspace.root, entry.directory);
3044
2781
  await safeParents(workspace, path);
3045
2782
  distributionAssert(
3046
- await exists2(path) && equalHashes(await directoryHashes(path), entry.files),
2783
+ await exists(path) && equalHashes(await directoryHashes(path), entry.files),
3047
2784
  "MODULE_LOCAL_CHANGES",
3048
2785
  `Module ${entry.id} has local changes or is missing. Preserve/merge those changes before updating.`
3049
2786
  );
@@ -3058,8 +2795,8 @@ async function validateInstalledModules(workspace) {
3058
2795
  for (const entry of lock.modules) {
3059
2796
  await assertUnmodified(workspace, entry);
3060
2797
  const manifest = JSON.parse(
3061
- await readFile8(
3062
- join6(workspace.root, entry.directory, "module.json"),
2798
+ await readFile7(
2799
+ join5(workspace.root, entry.directory, "module.json"),
3063
2800
  "utf8"
3064
2801
  )
3065
2802
  );
@@ -3071,278 +2808,15 @@ async function validateInstalledModules(workspace) {
3071
2808
  }
3072
2809
  return lock;
3073
2810
  }
3074
- async function rollback(workspace, journal) {
3075
- const transaction = join6(workspace.root, TRANSACTION);
3076
- validateLock(workspace, {
3077
- schemaVersion: 1,
3078
- modules: journal.entries.map((entry) => entry.installed)
3079
- });
3080
- for (const [index, entry] of [...journal.entries.entries()].reverse()) {
3081
- const destination = resolveInside(
3082
- workspace.root,
3083
- entry.installed.directory
3084
- );
3085
- await safeParents(workspace, destination);
3086
- const backup = join6(transaction, `backup-${index}`);
3087
- const moved = !await exists2(join6(transaction, `stage-${index}`));
3088
- if (moved && await exists2(destination)) {
3089
- const hashes = await directoryHashes(destination);
3090
- if (equalHashes(hashes, entry.installed.files))
3091
- await rm(destination, { recursive: true });
3092
- else
3093
- distributionAssert(
3094
- entry.previous && equalHashes(hashes, entry.previous.files) && !await exists2(backup),
3095
- "MODULE_RECOVERY_CONFLICT",
3096
- `Recovery preserved modified files in ${entry.installed.directory}.`
3097
- );
3098
- }
3099
- if (await exists2(backup)) {
3100
- distributionAssert(
3101
- !await exists2(destination),
3102
- "MODULE_RECOVERY_CONFLICT",
3103
- "Recovery destination is occupied."
3104
- );
3105
- await safeParents(workspace, backup);
3106
- await rename(backup, destination);
3107
- }
3108
- }
3109
- const lockPath = join6(workspace.root, LOCK);
3110
- await safeParents(workspace, lockPath);
3111
- const current = await exists2(lockPath) ? await readFile8(lockPath, "utf8") : null;
3112
- distributionAssert(
3113
- current === journal.previous || current === journal.next,
3114
- "MODULE_RECOVERY_CONFLICT",
3115
- "Lockfile changed during installation; recovery preserved it."
3116
- );
3117
- if (journal.previous === null) await rm(lockPath, { force: true });
3118
- else await writeFile(lockPath, journal.previous);
3119
- }
3120
- async function installModule(workspace, options) {
3121
- const transaction = join6(workspace.root, TRANSACTION);
3122
- await safeParents(workspace, transaction);
3123
- distributionAssert(
3124
- !await exists2(transaction),
3125
- "MODULE_INSTALL_BUSY",
3126
- "A module installation is active or interrupted. Use module recover after its process exits."
3127
- );
3128
- const source = await loadModuleCatalog(options.registry, options.fetcher);
3129
- const snapshot = await readLock(workspace);
3130
- const manifests = [];
3131
- for (const root of moduleRoots(workspace)) await safeParents(workspace, root);
3132
- const files = await findModuleFiles(workspace);
3133
- for (const file of files)
3134
- manifests.push(JSON.parse(await readFile8(file, "utf8")));
3135
- const id = options.target.split("@")[0];
3136
- const previous = snapshot.lock.modules.find((entry) => entry.id === id);
3137
- distributionAssert(
3138
- previous || !manifests.some((manifest) => manifest.id === id),
3139
- "MODULE_NOT_MANAGED",
3140
- `Module ${id} already exists as unmanaged workspace/package source.`
3141
- );
3142
- if (options.update) {
3143
- distributionAssert(
3144
- previous,
3145
- "MODULE_NOT_MANAGED",
3146
- `Module ${id} is not managed by this installer.`
3147
- );
3148
- await assertUnmodified(workspace, previous);
3149
- }
3150
- const retained = options.update ? manifests.filter((manifest) => manifest.id !== id) : manifests;
3151
- const releases = resolveModuleReleases(
3152
- source.catalog,
3153
- options.target,
3154
- retained
3155
- );
3156
- if (options.update) {
3157
- const next = releases.find((release) => release.manifest.id === id);
3158
- distributionAssert(
3159
- next && compareModuleVersions(next.manifest.version, previous.version) <= 0,
3160
- "MODULE_DOWNGRADE_FORBIDDEN",
3161
- "Module downgrades are not supported."
3162
- );
3163
- }
3164
- const registry = createModuleRegistry(
3165
- [...retained, ...releases.map((release) => release.manifest)].map(
3166
- (manifest) => ({ manifest })
3167
- )
3168
- );
3169
- void registry;
3170
- if (!releases.length) {
3171
- if (previous) await assertUnmodified(workspace, previous);
3172
- return { applied: false, modules: [], activationRequired: false };
3173
- }
3174
- const entries = [];
3175
- let totalBytes = 0;
3176
- for (const release of releases) {
3177
- const bytes = await readRelease(source, release, options.fetcher);
3178
- totalBytes += bytes.length;
3179
- distributionAssert(
3180
- totalBytes <= 96 * 1024 * 1024,
3181
- "MODULE_INSTALL_LIMIT",
3182
- "The dependency closure exceeds the installation size limit."
3183
- );
3184
- distributionAssert(
3185
- hashBytes(bytes) === release.sha256,
3186
- "MODULE_ARTIFACT_DIGEST",
3187
- `Artifact digest mismatch for ${release.manifest.id}.`
3188
- );
3189
- const artifact = validateModuleArtifact(JSON.parse(bytes.toString("utf8")));
3190
- distributionAssert(
3191
- JSON.stringify(artifact.manifest) === JSON.stringify(release.manifest),
3192
- "MODULE_ARTIFACT_IDENTITY",
3193
- "Catalog and artifact manifest differ."
3194
- );
3195
- const old = snapshot.lock.modules.find((entry) => entry.id === release.manifest.id) ?? null;
3196
- const destination = old ? resolveInside(workspace.root, old.directory) : join6(
3197
- moduleRoots(workspace)[0],
3198
- release.manifest.package.replace("@flowdular/module-", "")
3199
- );
3200
- await safeParents(workspace, destination);
3201
- if (old) {
3202
- distributionAssert(
3203
- options.update && old.id === id,
3204
- "MODULE_ALREADY_INSTALLED",
3205
- `Module ${old.id} is already managed.`
3206
- );
3207
- await assertUnmodified(workspace, old);
3208
- } else
3209
- distributionAssert(
3210
- !await exists2(destination),
3211
- "MODULE_DESTINATION_EXISTS",
3212
- `Destination exists: ${relative5(workspace.root, destination)}`
3213
- );
3214
- const hashes = Object.fromEntries(
3215
- artifact.files.map((file) => [file.path, file.sha256])
3216
- );
3217
- if (old) {
3218
- for (const [path, hash] of Object.entries(old.files))
3219
- if (path.startsWith("migrations/"))
3220
- distributionAssert(
3221
- hashes[path] === hash,
3222
- "MODULE_MIGRATION_CHANGED",
3223
- `Update changes historical migration ${path}.`
3224
- );
3225
- }
3226
- entries.push({
3227
- release,
3228
- artifact,
3229
- previous: old,
3230
- installed: {
3231
- id: release.manifest.id,
3232
- version: release.manifest.version,
3233
- directory: relative5(workspace.root, destination),
3234
- artifact: release.artifact,
3235
- sha256: release.sha256,
3236
- sourceCommit: release.sourceCommit,
3237
- files: hashes
3238
- }
3239
- });
3240
- }
3241
- const nextLock = {
3242
- schemaVersion: 1,
3243
- modules: [
3244
- ...snapshot.lock.modules.filter(
3245
- (entry) => !entries.some((next) => next.installed.id === entry.id)
3246
- ),
3247
- ...entries.map((entry) => entry.installed)
3248
- ].sort((a, b) => a.id.localeCompare(b.id))
3249
- };
3250
- validateLock(workspace, nextLock);
3251
- if (!options.apply)
3252
- return {
3253
- applied: false,
3254
- modules: entries.map((entry) => entry.installed),
3255
- activationRequired: true
3256
- };
3257
- await mkdir(transaction);
3258
- const journal = {
3259
- schemaVersion: 1,
3260
- pid: process.pid,
3261
- previous: snapshot.raw,
3262
- next: JSON.stringify(nextLock, null, " ") + "\n",
3263
- entries: entries.map((entry) => ({
3264
- installed: entry.installed,
3265
- previous: entry.previous
3266
- }))
3267
- };
3268
- let journalWritten = false;
3269
- try {
3270
- await writeFile(
3271
- join6(transaction, "owner.json"),
3272
- JSON.stringify({ schemaVersion: 1, pid: process.pid })
3273
- );
3274
- for (const [index, entry] of entries.entries()) {
3275
- const stage = join6(transaction, `stage-${index}`);
3276
- await mkdir(stage);
3277
- for (const file of entry.artifact.files) {
3278
- const path = join6(stage, file.path);
3279
- await mkdir(dirname6(path), { recursive: true });
3280
- await writeFile(path, Buffer.from(file.content, "base64"), {
3281
- flag: "wx"
3282
- });
3283
- }
3284
- const issues = await moduleLayoutIssues(stage, entry.artifact.manifest);
3285
- distributionAssert(
3286
- !issues.some((issue2) => issue2.severity === "error"),
3287
- "MODULE_LAYOUT_INVALID",
3288
- `Invalid module layout: ${issues.map((issue2) => issue2.message).join("; ")}`
3289
- );
3290
- }
3291
- await writeFile(join6(transaction, "journal.tmp"), JSON.stringify(journal));
3292
- await rename(
3293
- join6(transaction, "journal.tmp"),
3294
- join6(transaction, "journal.json")
3295
- );
3296
- journalWritten = true;
3297
- distributionAssert(
3298
- (await readLock(workspace)).raw === snapshot.raw,
3299
- "MODULE_CONCURRENT_CHANGE",
3300
- "Module lock changed during planning."
3301
- );
3302
- for (const [index, entry] of entries.entries()) {
3303
- const destination = resolveInside(
3304
- workspace.root,
3305
- entry.installed.directory
3306
- );
3307
- await safeParents(workspace, destination);
3308
- if (entry.previous) {
3309
- await assertUnmodified(workspace, entry.previous);
3310
- await rename(destination, join6(transaction, `backup-${index}`));
3311
- } else
3312
- distributionAssert(
3313
- !await exists2(destination),
3314
- "MODULE_DESTINATION_EXISTS",
3315
- `Destination appeared during installation: ${entry.installed.directory}`
3316
- );
3317
- await mkdir(dirname6(destination), { recursive: true });
3318
- await rename(join6(transaction, `stage-${index}`), destination);
3319
- }
3320
- await writeFile(join6(transaction, "next-lock.json"), journal.next);
3321
- await rename(
3322
- join6(transaction, "next-lock.json"),
3323
- join6(workspace.root, LOCK)
3324
- );
3325
- } catch (error) {
3326
- if (journalWritten) await rollback(workspace, journal);
3327
- await rm(transaction, { recursive: true });
3328
- throw error;
3329
- }
3330
- await rm(transaction, { recursive: true });
3331
- return {
3332
- applied: true,
3333
- modules: entries.map((entry) => entry.installed),
3334
- activationRequired: true
3335
- };
3336
- }
3337
2811
 
3338
2812
  // src/module-sync.ts
3339
2813
  import { spawnSync } from "node:child_process";
3340
- import { access as access3, mkdir as mkdir2, readFile as readFile9, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
3341
- import { dirname as dirname7, join as join8 } from "node:path";
2814
+ import { access as access2, mkdir as mkdir2, readFile as readFile8, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
2815
+ import { dirname as dirname5, join as join7 } from "node:path";
3342
2816
 
3343
2817
  // src/format.ts
3344
2818
  import { createRequire as createRequire3 } from "node:module";
3345
- import { join as join7 } from "node:path";
2819
+ import { join as join6 } from "node:path";
3346
2820
  import { pathToFileURL } from "node:url";
3347
2821
  var DEFAULT_OPTIONS = {
3348
2822
  useTabs: true,
@@ -3351,7 +2825,7 @@ var DEFAULT_OPTIONS = {
3351
2825
  };
3352
2826
  var TSRX_PLUGIN = "@tsrx/prettier-plugin";
3353
2827
  function resolveFrom(workspaceRoot, specifier) {
3354
- for (const base of [join7(workspaceRoot, "package.json"), import.meta.url]) {
2828
+ for (const base of [join6(workspaceRoot, "package.json"), import.meta.url]) {
3355
2829
  try {
3356
2830
  return createRequire3(base).resolve(specifier);
3357
2831
  } catch {
@@ -3379,7 +2853,7 @@ async function loadWorkspaceFormatter(workspaceRoot) {
3379
2853
  return null;
3380
2854
  }
3381
2855
  const configured = await prettier.resolveConfig(
3382
- join7(workspaceRoot, "package.json"),
2856
+ join6(workspaceRoot, "package.json"),
3383
2857
  { editorconfig: false }
3384
2858
  );
3385
2859
  const { plugins: _ignored, ...options } = configured ?? DEFAULT_OPTIONS;
@@ -3468,7 +2942,7 @@ async function loadManifests(workspace) {
3468
2942
  const descriptors = /* @__PURE__ */ new Map();
3469
2943
  for (const file of files) {
3470
2944
  const manifest = JSON.parse(
3471
- await readFile9(file, "utf8")
2945
+ await readFile8(file, "utf8")
3472
2946
  );
3473
2947
  if (!validators.module(manifest))
3474
2948
  throw new Error(`Invalid module manifest: ${file}`);
@@ -3573,7 +3047,7 @@ async function takeSnapshots(paths) {
3573
3047
  return Promise.all(
3574
3048
  paths.map(async (path) => {
3575
3049
  try {
3576
- return { path, content: await readFile9(path, "utf8") };
3050
+ return { path, content: await readFile8(path, "utf8") };
3577
3051
  } catch (error) {
3578
3052
  if (error.code === "ENOENT") {
3579
3053
  return { path, content: void 0 };
@@ -3589,7 +3063,7 @@ async function restoreSnapshots(snapshots) {
3589
3063
  await rm2(snapshot.path, { force: true });
3590
3064
  continue;
3591
3065
  }
3592
- await mkdir2(dirname7(snapshot.path), { recursive: true });
3066
+ await mkdir2(dirname5(snapshot.path), { recursive: true });
3593
3067
  await writeFile2(snapshot.path, snapshot.content);
3594
3068
  }
3595
3069
  }
@@ -3619,7 +3093,7 @@ async function syncPlatformModules(workspace, apply, enabledOverride) {
3619
3093
  content: generateClientComposition(modules, sdk, applicationPath)
3620
3094
  }
3621
3095
  ].map(async (target) => {
3622
- const absolute = join8(workspace.root, target.path);
3096
+ const absolute = join7(workspace.root, target.path);
3623
3097
  return {
3624
3098
  ...target,
3625
3099
  content: formatter ? await formatter.format(absolute, target.content) : target.content
@@ -3628,15 +3102,15 @@ async function syncPlatformModules(workspace, apply, enabledOverride) {
3628
3102
  );
3629
3103
  const files = [];
3630
3104
  for (const target of targets) {
3631
- const absolute = join8(workspace.root, target.path);
3105
+ const absolute = join7(workspace.root, target.path);
3632
3106
  let current = "";
3633
3107
  try {
3634
- current = await readFile9(absolute, "utf8");
3108
+ current = await readFile8(absolute, "utf8");
3635
3109
  } catch {
3636
3110
  }
3637
3111
  const changed = current !== target.content;
3638
3112
  if (apply && changed) {
3639
- await mkdir2(dirname7(absolute), { recursive: true });
3113
+ await mkdir2(dirname5(absolute), { recursive: true });
3640
3114
  await writeFile2(absolute, target.content);
3641
3115
  }
3642
3116
  files.push({ path: target.path, changed });
@@ -3660,9 +3134,9 @@ async function enableModule(workspace, id, apply) {
3660
3134
  const currentSet = new Set(current);
3661
3135
  const newlyEnabled = enabled.filter((moduleId) => !currentSet.has(moduleId));
3662
3136
  const configChanged = !equalIds(current, enabled);
3663
- const platformPackagePath = join8(workspace.root, "platform/package.json");
3137
+ const platformPackagePath = join7(workspace.root, "platform/package.json");
3664
3138
  const platformPackage = JSON.parse(
3665
- await readFile9(platformPackagePath, "utf8")
3139
+ await readFile8(platformPackagePath, "utf8")
3666
3140
  );
3667
3141
  const dependencies = platformPackage.dependencies ?? {};
3668
3142
  const platformDependenciesAdded = modules.map((module) => npmPackage(module.package)).filter((packageName) => !(packageName in dependencies)).sort((left, right) => left.localeCompare(right));
@@ -3679,8 +3153,8 @@ async function enableModule(workspace, id, apply) {
3679
3153
  }
3680
3154
  const linked = async (module) => {
3681
3155
  try {
3682
- await access3(
3683
- join8(
3156
+ await access2(
3157
+ join7(
3684
3158
  workspace.root,
3685
3159
  "platform/node_modules",
3686
3160
  npmPackage(module.package)
@@ -3698,16 +3172,16 @@ async function enableModule(workspace, id, apply) {
3698
3172
  if (apply) {
3699
3173
  const snapshots = await takeSnapshots([
3700
3174
  workspace.configPath,
3701
- join8(workspace.root, "pnpm-lock.yaml"),
3175
+ join7(workspace.root, "pnpm-lock.yaml"),
3702
3176
  platformPackagePath,
3703
3177
  ...Object.values(GENERATED_FILES).map(
3704
- (path) => join8(workspace.root, path)
3178
+ (path) => join7(workspace.root, path)
3705
3179
  )
3706
3180
  ]);
3707
3181
  try {
3708
3182
  if (configChanged) {
3709
3183
  const config = JSON.parse(
3710
- await readFile9(workspace.configPath, "utf8")
3184
+ await readFile8(workspace.configPath, "utf8")
3711
3185
  );
3712
3186
  const configModules = config.modules ?? {};
3713
3187
  configModules.enabled = enabled;
@@ -3762,7 +3236,6 @@ ${output}` : ""}`
3762
3236
  export {
3763
3237
  enableModule,
3764
3238
  hashBytes,
3765
- installModule,
3766
3239
  loadModuleCatalog,
3767
3240
  packModule,
3768
3241
  readModuleSource,