@agentic-kit/db-tools 0.2.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.
Files changed (131) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +138 -0
  3. package/app-workspace.d.ts +16 -0
  4. package/app-workspace.js +145 -0
  5. package/context.d.ts +62 -0
  6. package/context.js +282 -0
  7. package/db-probe.d.ts +30 -0
  8. package/db-probe.js +50 -0
  9. package/esm/app-workspace.d.ts +16 -0
  10. package/esm/app-workspace.js +134 -0
  11. package/esm/context.d.ts +62 -0
  12. package/esm/context.js +270 -0
  13. package/esm/db-probe.d.ts +30 -0
  14. package/esm/db-probe.js +47 -0
  15. package/esm/host.d.ts +115 -0
  16. package/esm/host.js +23 -0
  17. package/esm/index.d.ts +20 -0
  18. package/esm/index.js +52 -0
  19. package/esm/policy/add-policies-to-table.d.ts +26 -0
  20. package/esm/policy/add-policies-to-table.js +66 -0
  21. package/esm/policy/provision-helpers.d.ts +38 -0
  22. package/esm/policy/provision-helpers.js +50 -0
  23. package/esm/provision-database/credential.d.ts +14 -0
  24. package/esm/provision-database/credential.js +19 -0
  25. package/esm/provision-database/env-file.d.ts +37 -0
  26. package/esm/provision-database/env-file.js +85 -0
  27. package/esm/provision-database/manifest.d.ts +10 -0
  28. package/esm/provision-database/manifest.js +48 -0
  29. package/esm/provision-database/pg-fixups.d.ts +16 -0
  30. package/esm/provision-database/pg-fixups.js +132 -0
  31. package/esm/provision-database/preset-match.d.ts +13 -0
  32. package/esm/provision-database/preset-match.js +66 -0
  33. package/esm/provision-database/presets.d.ts +4 -0
  34. package/esm/provision-database/presets.js +13 -0
  35. package/esm/provision-database/request-database.d.ts +61 -0
  36. package/esm/provision-database/request-database.js +130 -0
  37. package/esm/provision-database/resolve.d.ts +27 -0
  38. package/esm/provision-database/resolve.js +49 -0
  39. package/esm/records/meta.d.ts +41 -0
  40. package/esm/records/meta.js +167 -0
  41. package/esm/run-codegen/barrels.d.ts +3 -0
  42. package/esm/run-codegen/barrels.js +48 -0
  43. package/esm/run-codegen/endpoints.d.ts +8 -0
  44. package/esm/run-codegen/endpoints.js +46 -0
  45. package/esm/schema-resolve.d.ts +23 -0
  46. package/esm/schema-resolve.js +69 -0
  47. package/esm/tool-schema.d.ts +3 -0
  48. package/esm/tool-schema.js +10 -0
  49. package/esm/tools/add-policies.d.ts +24 -0
  50. package/esm/tools/add-policies.js +94 -0
  51. package/esm/tools/add-records.d.ts +18 -0
  52. package/esm/tools/add-records.js +122 -0
  53. package/esm/tools/add-relation-schema.d.ts +29 -0
  54. package/esm/tools/add-relation-schema.js +56 -0
  55. package/esm/tools/add-relation.d.ts +10 -0
  56. package/esm/tools/add-relation.js +91 -0
  57. package/esm/tools/create-api-key.d.ts +25 -0
  58. package/esm/tools/create-api-key.js +214 -0
  59. package/esm/tools/describe-schema.d.ts +19 -0
  60. package/esm/tools/describe-schema.js +130 -0
  61. package/esm/tools/manage-entity-types.d.ts +40 -0
  62. package/esm/tools/manage-entity-types.js +201 -0
  63. package/esm/tools/mutations.d.ts +35 -0
  64. package/esm/tools/mutations.js +230 -0
  65. package/esm/tools/provision-blueprint.d.ts +21 -0
  66. package/esm/tools/provision-blueprint.js +109 -0
  67. package/esm/tools/provision-database.d.ts +18 -0
  68. package/esm/tools/provision-database.js +260 -0
  69. package/esm/tools/run-codegen.d.ts +12 -0
  70. package/esm/tools/run-codegen.js +137 -0
  71. package/esm/tools/templates.d.ts +71 -0
  72. package/esm/tools/templates.js +331 -0
  73. package/host.d.ts +115 -0
  74. package/host.js +28 -0
  75. package/index.d.ts +20 -0
  76. package/index.js +76 -0
  77. package/package.json +49 -0
  78. package/policy/add-policies-to-table.d.ts +26 -0
  79. package/policy/add-policies-to-table.js +71 -0
  80. package/policy/provision-helpers.d.ts +38 -0
  81. package/policy/provision-helpers.js +58 -0
  82. package/provision-database/credential.d.ts +14 -0
  83. package/provision-database/credential.js +22 -0
  84. package/provision-database/env-file.d.ts +37 -0
  85. package/provision-database/env-file.js +91 -0
  86. package/provision-database/manifest.d.ts +10 -0
  87. package/provision-database/manifest.js +56 -0
  88. package/provision-database/pg-fixups.d.ts +16 -0
  89. package/provision-database/pg-fixups.js +170 -0
  90. package/provision-database/preset-match.d.ts +13 -0
  91. package/provision-database/preset-match.js +70 -0
  92. package/provision-database/presets.d.ts +4 -0
  93. package/provision-database/presets.js +17 -0
  94. package/provision-database/request-database.d.ts +61 -0
  95. package/provision-database/request-database.js +134 -0
  96. package/provision-database/resolve.d.ts +27 -0
  97. package/provision-database/resolve.js +53 -0
  98. package/records/meta.d.ts +41 -0
  99. package/records/meta.js +172 -0
  100. package/run-codegen/barrels.d.ts +3 -0
  101. package/run-codegen/barrels.js +54 -0
  102. package/run-codegen/endpoints.d.ts +8 -0
  103. package/run-codegen/endpoints.js +51 -0
  104. package/schema-resolve.d.ts +23 -0
  105. package/schema-resolve.js +75 -0
  106. package/tool-schema.d.ts +3 -0
  107. package/tool-schema.js +13 -0
  108. package/tools/add-policies.d.ts +24 -0
  109. package/tools/add-policies.js +97 -0
  110. package/tools/add-records.d.ts +18 -0
  111. package/tools/add-records.js +125 -0
  112. package/tools/add-relation-schema.d.ts +29 -0
  113. package/tools/add-relation-schema.js +60 -0
  114. package/tools/add-relation.d.ts +10 -0
  115. package/tools/add-relation.js +94 -0
  116. package/tools/create-api-key.d.ts +25 -0
  117. package/tools/create-api-key.js +220 -0
  118. package/tools/describe-schema.d.ts +19 -0
  119. package/tools/describe-schema.js +133 -0
  120. package/tools/manage-entity-types.d.ts +40 -0
  121. package/tools/manage-entity-types.js +206 -0
  122. package/tools/mutations.d.ts +35 -0
  123. package/tools/mutations.js +233 -0
  124. package/tools/provision-blueprint.d.ts +21 -0
  125. package/tools/provision-blueprint.js +112 -0
  126. package/tools/provision-database.d.ts +18 -0
  127. package/tools/provision-database.js +266 -0
  128. package/tools/run-codegen.d.ts +12 -0
  129. package/tools/run-codegen.js +141 -0
  130. package/tools/templates.d.ts +71 -0
  131. package/tools/templates.js +335 -0
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ // Client-side mirror of the backend's modules_hash normalization
3
+ // (constructive-db: metaschema_private.modules_hash), used to decide whether a
4
+ // resolved module set can be requested by preset slug — the warm-pool-eligible
5
+ // path — or must be sent as an explicit modules array:
6
+ // 1. A bare module name normalizes to [name, {}]; a missing/null options
7
+ // slot normalizes to {}.
8
+ // 2. Options are compared verbatim after canonicalization (object keys
9
+ // sorted at every level) — no default-value normalization.
10
+ // 3. Entries are sorted so the caller's ordering never affects the match.
11
+ // 4. Duplicate normalized entries never match (the backend rejects them).
12
+ // Only equality within this process matters, so string sort order does not
13
+ // need to replicate PostgreSQL collation.
14
+ //
15
+ // The registry presets are the canonical definitions; the backend's
16
+ // db_presets catalog is synced from them. A slug match assumes that sync —
17
+ // preset drift would provision the backend's version of the set.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.matchPresetSlug = matchPresetSlug;
20
+ exports.selectProvisionRequest = selectProvisionRequest;
21
+ const presets_1 = require("./presets");
22
+ function canonicalize(value) {
23
+ if (Array.isArray(value))
24
+ return value.map(canonicalize);
25
+ if (value && typeof value === 'object') {
26
+ const sorted = {};
27
+ for (const key of Object.keys(value).sort()) {
28
+ sorted[key] = canonicalize(value[key]);
29
+ }
30
+ return sorted;
31
+ }
32
+ return value;
33
+ }
34
+ function normalizedEntries(list) {
35
+ const entries = list.map((m) => {
36
+ const name = Array.isArray(m) ? m[0] : m;
37
+ const options = Array.isArray(m) ? (m[1] ?? {}) : {};
38
+ return JSON.stringify([name, canonicalize(options)]);
39
+ });
40
+ entries.sort();
41
+ for (let i = 1; i < entries.length; i++) {
42
+ if (entries[i] === entries[i - 1])
43
+ return null;
44
+ }
45
+ return entries;
46
+ }
47
+ /**
48
+ * The slug of the registry preset whose module set equals `resolved` under
49
+ * the backend's hash semantics, or undefined when no preset matches.
50
+ */
51
+ function matchPresetSlug(resolved) {
52
+ const target = normalizedEntries(resolved)?.join('\n');
53
+ if (!target)
54
+ return undefined;
55
+ for (const preset of presets_1.allModulePresets) {
56
+ if (normalizedEntries(preset.modules)?.join('\n') === target) {
57
+ return preset.name;
58
+ }
59
+ }
60
+ return undefined;
61
+ }
62
+ /**
63
+ * Build the requestDatabase input branch: presetSlug when the resolved set
64
+ * equals a registry preset (warm-pool eligible), else the explicit modules
65
+ * array — never both.
66
+ */
67
+ function selectProvisionRequest(resolved) {
68
+ const presetSlug = matchPresetSlug(resolved);
69
+ return presetSlug ? { presetSlug } : { modules: resolved };
70
+ }
@@ -0,0 +1,4 @@
1
+ import { allModulePresets, getModulePreset, type ModulePreset } from 'node-type-registry';
2
+ export type ProvisionModule = string | [string, Record<string, unknown>];
3
+ export declare const DEFAULT_PROVISION_PRESET: string;
4
+ export { allModulePresets, getModulePreset, type ModulePreset };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getModulePreset = exports.allModulePresets = exports.DEFAULT_PROVISION_PRESET = void 0;
4
+ // Provision module presets — the canonical source of truth.
5
+ //
6
+ // The module list is NEVER hand-copied here. It is imported from the pinned
7
+ // `node-type-registry` package (the same presets the backend and dashboard use),
8
+ // so bumping that dependency is how the base module set moves. Hosts customize
9
+ // on top with an ordered overlay (see `resolve.ts`) rather than editing a list.
10
+ const node_type_registry_1 = require("node-type-registry");
11
+ Object.defineProperty(exports, "allModulePresets", { enumerable: true, get: function () { return node_type_registry_1.allModulePresets; } });
12
+ Object.defineProperty(exports, "getModulePreset", { enumerable: true, get: function () { return node_type_registry_1.getModulePreset; } });
13
+ // Default base preset. `b2b:storage` = the full multi-tenant B2B shape plus file
14
+ // storage, matching Constructive's app shape. It carries the app-scoped secret,
15
+ // identity-provider, and storage modules the backend's provisioning requires —
16
+ // the drift the old hand-copied list caused (a dropped, then bare/unscoped, set).
17
+ exports.DEFAULT_PROVISION_PRESET = node_type_registry_1.PresetB2bStorage.name;
@@ -0,0 +1,61 @@
1
+ import type { ProvisionModule } from './presets';
2
+ /**
3
+ * Exactly one of presetSlug or modules — requestDatabase rejects both/neither
4
+ * (REQUEST_DATABASE_INVALID_INPUT). `selectProvisionRequest` (preset-match.ts)
5
+ * builds this from the resolved module set.
6
+ */
7
+ export type ProvisionRequest = {
8
+ presetSlug: string;
9
+ } | {
10
+ modules: ProvisionModule[];
11
+ };
12
+ export type ProvisionTicket = {
13
+ id?: string | null;
14
+ status?: string | null;
15
+ bootstrapStatus?: string | null;
16
+ databaseId?: string | null;
17
+ errorMessage?: string | null;
18
+ bootstrapError?: string | null;
19
+ };
20
+ /**
21
+ * Request + poll seam, injectable for tests (pattern: db-probe's
22
+ * ProbeExecutor). The default executor drives the SDK clients.
23
+ */
24
+ export type ProvisionRequestExecutor = {
25
+ request(input: {
26
+ databaseName: string;
27
+ subdomain: string;
28
+ domain: string;
29
+ request: ProvisionRequest;
30
+ }): Promise<ProvisionTicket>;
31
+ poll(ticketId: string): Promise<ProvisionTicket | null>;
32
+ };
33
+ export declare const POLL_INTERVAL_MS = 2000;
34
+ /** Stays under airpage's 5-minute per-tool timeout. */
35
+ export declare const POLL_DEADLINE_MS = 240000;
36
+ /**
37
+ * Consecutive unreadable polls tolerated before giving up. Provisioning keeps
38
+ * running server-side, so a dropped request or a momentary 502 must not abort
39
+ * a creation that is about to succeed.
40
+ */
41
+ export declare const MAX_CONSECUTIVE_POLL_FAILURES = 3;
42
+ /**
43
+ * Request a database and resolve only when it is fully usable: ticket
44
+ * `status` AND `bootstrapStatus` both 'completed'. The ticket the mutation
45
+ * returns is checked before the first sleep, so a warm claim with bootstrap
46
+ * already done never waits. `status: 'failed'` surfaces `errorMessage`;
47
+ * `bootstrapStatus: 'failed'` surfaces `bootstrapError`.
48
+ */
49
+ export declare function requestDatabaseProvision(args: {
50
+ apiEndpoint: string;
51
+ modulesEndpoint: string;
52
+ bearer: string;
53
+ databaseName: string;
54
+ domain: string;
55
+ request: ProvisionRequest;
56
+ executor?: ProvisionRequestExecutor;
57
+ pollIntervalMs?: number;
58
+ pollDeadlineMs?: number;
59
+ }): Promise<{
60
+ databaseId: string;
61
+ }>;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ // Provision a database through the api endpoint's requestDatabase mutation —
3
+ // the pool-aware entry point. The mutation returns a ticket (a
4
+ // database_provision_module row) immediately: a warm-pool claim arrives
5
+ // 'completed' with the deferred owner bootstrap still pending; a cold request
6
+ // arrives 'pending' and is fulfilled asynchronously by the backend's jobs
7
+ // worker. The ticket is polled on the MODULES endpoint (the api endpoint has
8
+ // no ticket query) until BOTH status and bootstrapStatus are 'completed': pi
9
+ // hands credentials to the agent as soon as this resolves, and until owner
10
+ // bootstrap completes the new database has zero users, so its per-DB API
11
+ // would reject the owner token under RLS. requestDatabase owns the database
12
+ // to the JWT user — there is no owner input. The generated ORM's default
13
+ // adapter is plain fetch, so the SDK's FetchAdapter is injected to keep
14
+ // *.localhost DNS/Host routing working in Node.
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.MAX_CONSECUTIVE_POLL_FAILURES = exports.POLL_DEADLINE_MS = exports.POLL_INTERVAL_MS = void 0;
17
+ exports.requestDatabaseProvision = requestDatabaseProvision;
18
+ const sdk_1 = require("@constructive-io/sdk");
19
+ exports.POLL_INTERVAL_MS = 2_000;
20
+ /** Stays under airpage's 5-minute per-tool timeout. */
21
+ exports.POLL_DEADLINE_MS = 240_000;
22
+ /**
23
+ * Consecutive unreadable polls tolerated before giving up. Provisioning keeps
24
+ * running server-side, so a dropped request or a momentary 502 must not abort
25
+ * a creation that is about to succeed.
26
+ */
27
+ exports.MAX_CONSECUTIVE_POLL_FAILURES = 3;
28
+ const TICKET_SELECT = {
29
+ id: true,
30
+ status: true,
31
+ bootstrapStatus: true,
32
+ databaseId: true,
33
+ errorMessage: true,
34
+ bootstrapError: true,
35
+ };
36
+ function createSdkExecutor(args) {
37
+ const headers = { Authorization: `Bearer ${args.bearer}` };
38
+ const apiClient = sdk_1.api.createClient({ adapter: new sdk_1.api.FetchAdapter(args.apiEndpoint, headers) });
39
+ const modulesClient = sdk_1.modules.createClient({
40
+ adapter: new sdk_1.api.FetchAdapter(args.modulesEndpoint, headers),
41
+ });
42
+ return {
43
+ async request(input) {
44
+ const requestInput = {
45
+ databaseName: input.databaseName,
46
+ subdomain: input.subdomain,
47
+ domain: input.domain,
48
+ ...('presetSlug' in input.request
49
+ ? { presetSlug: input.request.presetSlug }
50
+ : { modules: input.request.modules }),
51
+ };
52
+ const result = await apiClient.mutation
53
+ .requestDatabase({ input: requestInput }, { select: { result: { select: TICKET_SELECT } } })
54
+ .unwrap();
55
+ const ticket = result.requestDatabase?.result;
56
+ if (!ticket)
57
+ throw new Error('requestDatabase returned no provision ticket.');
58
+ return ticket;
59
+ },
60
+ async poll(ticketId) {
61
+ const result = await modulesClient.databaseProvisionModule
62
+ .findOne({ id: ticketId, select: TICKET_SELECT })
63
+ .unwrap();
64
+ return result.databaseProvisionModule ?? null;
65
+ },
66
+ };
67
+ }
68
+ function sleep(ms) {
69
+ return new Promise((resolve) => setTimeout(resolve, ms));
70
+ }
71
+ /**
72
+ * Request a database and resolve only when it is fully usable: ticket
73
+ * `status` AND `bootstrapStatus` both 'completed'. The ticket the mutation
74
+ * returns is checked before the first sleep, so a warm claim with bootstrap
75
+ * already done never waits. `status: 'failed'` surfaces `errorMessage`;
76
+ * `bootstrapStatus: 'failed'` surfaces `bootstrapError`.
77
+ */
78
+ async function requestDatabaseProvision(args) {
79
+ const executor = args.executor ??
80
+ createSdkExecutor({
81
+ apiEndpoint: args.apiEndpoint,
82
+ modulesEndpoint: args.modulesEndpoint,
83
+ bearer: args.bearer,
84
+ });
85
+ const intervalMs = args.pollIntervalMs ?? exports.POLL_INTERVAL_MS;
86
+ const deadlineMs = args.pollDeadlineMs ?? exports.POLL_DEADLINE_MS;
87
+ let ticket = await executor.request({
88
+ databaseName: args.databaseName,
89
+ subdomain: args.databaseName,
90
+ domain: args.domain,
91
+ request: args.request,
92
+ });
93
+ const ticketId = ticket.id;
94
+ if (!ticketId)
95
+ throw new Error('requestDatabase returned a provision ticket without an id.');
96
+ const deadline = Date.now() + deadlineMs;
97
+ let failures = 0;
98
+ for (;;) {
99
+ if (ticket.status === 'failed') {
100
+ throw new Error(ticket.errorMessage ?? 'provisioning failed');
101
+ }
102
+ if (ticket.bootstrapStatus === 'failed') {
103
+ throw new Error(ticket.bootstrapError ?? 'owner bootstrap failed');
104
+ }
105
+ if (ticket.status === 'completed' && ticket.bootstrapStatus === 'completed') {
106
+ if (!ticket.databaseId)
107
+ throw new Error('provisioning completed but returned no databaseId.');
108
+ return { databaseId: ticket.databaseId };
109
+ }
110
+ if (Date.now() >= deadline) {
111
+ throw new Error(`provisioning timed out after ${Math.round(deadlineMs / 1000)}s (status: ${ticket.status ?? 'unknown'}, bootstrapStatus: ${ticket.bootstrapStatus ?? 'unknown'})`);
112
+ }
113
+ await sleep(intervalMs);
114
+ // A poll that throws (network blip, 502) or comes back empty is retried a
115
+ // few times rather than failing a provision that is still running.
116
+ let polled;
117
+ try {
118
+ polled = await executor.poll(ticketId);
119
+ }
120
+ catch (err) {
121
+ if (++failures > exports.MAX_CONSECUTIVE_POLL_FAILURES)
122
+ throw err;
123
+ continue;
124
+ }
125
+ if (!polled) {
126
+ if (++failures > exports.MAX_CONSECUTIVE_POLL_FAILURES) {
127
+ throw new Error(`provision ticket ${ticketId} could not be read back.`);
128
+ }
129
+ continue;
130
+ }
131
+ failures = 0;
132
+ ticket = polled;
133
+ }
134
+ }
@@ -0,0 +1,27 @@
1
+ import { type ProvisionModule } from './presets';
2
+ export interface ProvisionOverlay {
3
+ /**
4
+ * Base preset name to resolve from (e.g. 'b2b:storage', 'full'). The last
5
+ * layer that sets `preset` wins; if none set it, `DEFAULT_PROVISION_PRESET`
6
+ * is used.
7
+ */
8
+ preset?: string;
9
+ /**
10
+ * Modules to add. An entry whose key matches an existing module OVERRIDES it
11
+ * (e.g. re-declaring `storage_module` with app-specific options); a new key
12
+ * is appended.
13
+ */
14
+ add?: ProvisionModule[];
15
+ /**
16
+ * Module names or `name:scope` keys to drop from the base. A bare name
17
+ * removes every scope of that module; `name:scope` removes just that one.
18
+ */
19
+ remove?: string[];
20
+ }
21
+ /** Stable identity for a module entry: `name` or `name:scope` when scoped. */
22
+ export declare function moduleKey(m: ProvisionModule): string;
23
+ /**
24
+ * Resolve the effective module list from a pinned preset plus ordered overlays.
25
+ * Pure: no I/O, no host access. Later overlays win (last-write-wins by key).
26
+ */
27
+ export declare function resolveProvisionModules(layers?: ProvisionOverlay[]): ProvisionModule[];
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.moduleKey = moduleKey;
4
+ exports.resolveProvisionModules = resolveProvisionModules;
5
+ // Ordered-overlay resolution for provision modules.
6
+ //
7
+ // Mirrors the skills overlay model (`@agentic-kit/harness` skills manifest): a
8
+ // pinned base (a named preset from `node-type-registry`) is layered under zero
9
+ // or more overlays, later layers winning by module key. An empty overlay list
10
+ // resolves to the base preset unchanged.
11
+ const presets_1 = require("./presets");
12
+ function moduleName(m) {
13
+ return Array.isArray(m) ? m[0] : m;
14
+ }
15
+ function moduleScope(m) {
16
+ if (!Array.isArray(m))
17
+ return undefined;
18
+ const scope = m[1]?.scope;
19
+ return typeof scope === 'string' ? scope : undefined;
20
+ }
21
+ /** Stable identity for a module entry: `name` or `name:scope` when scoped. */
22
+ function moduleKey(m) {
23
+ const scope = moduleScope(m);
24
+ return scope ? `${moduleName(m)}:${scope}` : moduleName(m);
25
+ }
26
+ /**
27
+ * Resolve the effective module list from a pinned preset plus ordered overlays.
28
+ * Pure: no I/O, no host access. Later overlays win (last-write-wins by key).
29
+ */
30
+ function resolveProvisionModules(layers = []) {
31
+ const presetName = [...layers].reverse().find((l) => l.preset)?.preset ?? presets_1.DEFAULT_PROVISION_PRESET;
32
+ const preset = (0, presets_1.getModulePreset)(presetName);
33
+ if (!preset) {
34
+ const known = presets_1.allModulePresets.map((p) => p.name).join(', ');
35
+ throw new Error(`Unknown provision preset "${presetName}". Known presets: ${known}.`);
36
+ }
37
+ // Ordered map keyed by module identity preserves position while allowing
38
+ // overrides (same key) and appends (new key).
39
+ const resolved = new Map();
40
+ for (const m of preset.modules)
41
+ resolved.set(moduleKey(m), m);
42
+ for (const layer of layers) {
43
+ for (const target of layer.remove ?? []) {
44
+ for (const key of [...resolved.keys()]) {
45
+ if (key === target || key.split(':')[0] === target)
46
+ resolved.delete(key);
47
+ }
48
+ }
49
+ for (const m of layer.add ?? [])
50
+ resolved.set(moduleKey(m), m);
51
+ }
52
+ return [...resolved.values()];
53
+ }
@@ -0,0 +1,41 @@
1
+ import type { Table, TableInflection } from '@constructive-io/graphql-query/types/schema';
2
+ export declare const META_QUERY = "\n query DbToolsMeta {\n _meta {\n tables {\n name\n query { all one create update delete }\n inflection {\n allRows\n conditionType\n connection\n createInputType\n createPayloadType\n deletePayloadType\n edge\n filterType\n orderByType\n patchType\n tableType\n updatePayloadType\n }\n fields {\n name\n isNotNull\n hasDefault\n type {\n gqlType\n isArray\n pgType\n subtype\n isNotNull\n hasDefault\n }\n }\n }\n }\n }\n";
3
+ type RawType = {
4
+ gqlType: string;
5
+ isArray: boolean;
6
+ modifier?: string | number | null;
7
+ pgAlias?: string | null;
8
+ pgType?: string | null;
9
+ subtype?: string | null;
10
+ typmod?: number | null;
11
+ isNotNull?: boolean | null;
12
+ hasDefault?: boolean | null;
13
+ };
14
+ type RawField = {
15
+ name: string;
16
+ isNotNull?: boolean | null;
17
+ hasDefault?: boolean | null;
18
+ type: RawType;
19
+ };
20
+ type RawInflection = Partial<Record<keyof TableInflection, string | null>>;
21
+ type RawQuery = {
22
+ all: string | null;
23
+ one?: string | null;
24
+ create?: string | null;
25
+ update?: string | null;
26
+ delete?: string | null;
27
+ };
28
+ export type RawMetaTable = {
29
+ name: string;
30
+ query?: RawQuery | null;
31
+ inflection?: RawInflection | null;
32
+ fields?: (RawField | null)[] | null;
33
+ };
34
+ export type MetaResponse = {
35
+ _meta?: {
36
+ tables?: (RawMetaTable | null)[] | null;
37
+ } | null;
38
+ };
39
+ export declare function cleanTable(meta: RawMetaTable): Table;
40
+ export declare function findMetaTable(tables: RawMetaTable[], requested: string): RawMetaTable | undefined;
41
+ export {};
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.META_QUERY = void 0;
4
+ exports.cleanTable = cleanTable;
5
+ exports.findMetaTable = findMetaTable;
6
+ // Vendored from constructive-client's `@constructive-io/data` (not published to
7
+ // the registry). We query the per-DB data endpoint's `_meta` introspection and
8
+ // reshape each table into the `Table` shape `buildPostGraphileCreate` expects.
9
+ // Relations are intentionally left empty: the create-mutation builder only reads
10
+ // them to skip relational fields in the return selection, and every `_meta` field
11
+ // is a real scalar column, so selecting them all is valid.
12
+ //
13
+ // Only fields the live backend's `_meta` actually exposes are selected here —
14
+ // over-selecting (e.g. MetaType.modifier or MetaInflection.createField) fails
15
+ // GraphQL validation. The reshapers below tolerate the omitted fields.
16
+ exports.META_QUERY = `
17
+ query DbToolsMeta {
18
+ _meta {
19
+ tables {
20
+ name
21
+ query { all one create update delete }
22
+ inflection {
23
+ allRows
24
+ conditionType
25
+ connection
26
+ createInputType
27
+ createPayloadType
28
+ deletePayloadType
29
+ edge
30
+ filterType
31
+ orderByType
32
+ patchType
33
+ tableType
34
+ updatePayloadType
35
+ }
36
+ fields {
37
+ name
38
+ isNotNull
39
+ hasDefault
40
+ type {
41
+ gqlType
42
+ isArray
43
+ pgType
44
+ subtype
45
+ isNotNull
46
+ hasDefault
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ `;
53
+ function convertInflection(inflection) {
54
+ if (!inflection)
55
+ return undefined;
56
+ const s = (key) => inflection[key] ?? '';
57
+ const n = (key) => inflection[key] ?? null;
58
+ return {
59
+ allRows: s('allRows'),
60
+ allRowsSimple: s('allRowsSimple'),
61
+ conditionType: s('conditionType'),
62
+ connection: s('connection'),
63
+ createField: s('createField'),
64
+ createInputType: s('createInputType'),
65
+ createPayloadType: s('createPayloadType'),
66
+ deleteByPrimaryKey: n('deleteByPrimaryKey'),
67
+ deletePayloadType: s('deletePayloadType'),
68
+ edge: s('edge'),
69
+ edgeField: s('edgeField'),
70
+ enumType: s('enumType'),
71
+ filterType: n('filterType'),
72
+ inputType: s('inputType'),
73
+ orderByType: s('orderByType'),
74
+ patchField: s('patchField'),
75
+ patchType: n('patchType'),
76
+ tableFieldName: s('tableFieldName'),
77
+ tableType: s('tableType'),
78
+ typeName: s('typeName'),
79
+ updateByPrimaryKey: n('updateByPrimaryKey'),
80
+ updatePayloadType: n('updatePayloadType'),
81
+ };
82
+ }
83
+ function convertQuery(query) {
84
+ if (!query)
85
+ return undefined;
86
+ return {
87
+ all: query.all ?? '',
88
+ one: query.one ?? null,
89
+ create: query.create ?? '',
90
+ update: query.update ?? null,
91
+ delete: query.delete ?? null,
92
+ };
93
+ }
94
+ function cleanTable(meta) {
95
+ return {
96
+ name: meta.name,
97
+ inflection: convertInflection(meta.inflection),
98
+ query: convertQuery(meta.query),
99
+ fields: (meta.fields ?? [])
100
+ .filter((f) => Boolean(f))
101
+ .map((field) => ({
102
+ name: field.name,
103
+ type: {
104
+ gqlType: field.type.gqlType,
105
+ isArray: field.type.isArray,
106
+ modifier: field.type.modifier,
107
+ pgAlias: field.type.pgAlias,
108
+ pgType: field.type.pgType,
109
+ subtype: field.type.subtype,
110
+ typmod: field.type.typmod,
111
+ },
112
+ isNotNull: field.isNotNull ?? field.type.isNotNull ?? null,
113
+ hasDefault: field.hasDefault ?? field.type.hasDefault ?? null,
114
+ })),
115
+ relations: { belongsTo: [], hasOne: [], hasMany: [], manyToMany: [] },
116
+ };
117
+ }
118
+ function normalizeTableLookupKey(value) {
119
+ return (value ?? '')
120
+ .trim()
121
+ .toLowerCase()
122
+ .replace(/[^a-z0-9]/g, '');
123
+ }
124
+ function getTableLookupVariants(value) {
125
+ const normalized = normalizeTableLookupKey(value);
126
+ if (!normalized)
127
+ return [];
128
+ const variants = new Set([normalized]);
129
+ if (normalized.endsWith('y')) {
130
+ variants.add(`${normalized}s`);
131
+ variants.add(`${normalized.slice(0, -1)}ies`);
132
+ }
133
+ if (normalized.endsWith('ies')) {
134
+ variants.add(`${normalized.slice(0, -3)}y`);
135
+ }
136
+ if (/(s|x|z|ch|sh)$/.test(normalized))
137
+ variants.add(`${normalized}es`);
138
+ if (normalized.endsWith('es'))
139
+ variants.add(normalized.slice(0, -2));
140
+ if (!normalized.endsWith('s'))
141
+ variants.add(`${normalized}s`);
142
+ if (normalized.endsWith('s'))
143
+ variants.add(normalized.slice(0, -1));
144
+ return [...variants].filter(Boolean);
145
+ }
146
+ // Resolve a user-supplied table name (snake_case singular or plural) against the
147
+ // PascalCase-singular `_meta` table names, tolerating pluralization differences.
148
+ function findMetaTable(tables, requested) {
149
+ const index = new Map();
150
+ for (const table of tables) {
151
+ const keys = [
152
+ table.name,
153
+ table.query?.all,
154
+ table.query?.one,
155
+ table.inflection?.tableType,
156
+ table.inflection?.tableFieldName,
157
+ table.inflection?.allRows,
158
+ ];
159
+ for (const key of keys) {
160
+ for (const variant of getTableLookupVariants(key)) {
161
+ if (!index.has(variant))
162
+ index.set(variant, table);
163
+ }
164
+ }
165
+ }
166
+ for (const variant of getTableLookupVariants(requested)) {
167
+ const match = index.get(variant);
168
+ if (match)
169
+ return match;
170
+ }
171
+ return undefined;
172
+ }
@@ -0,0 +1,3 @@
1
+ /** Rewrite directory barrel specifiers under `root` in place. Returns the count
2
+ * of files changed. No-op (returns 0) when `root` does not exist. */
3
+ export declare function normalizeSdkBarrels(root: string): number;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ // Turbopack barrel normalization (TURBOPACK-BARREL-001).
3
+ //
4
+ // Codegen emits directory barrel re-exports like `export * from './hooks'` where
5
+ // `./hooks` is a directory with an index.ts. Next.js 16's Turbopack cannot
6
+ // resolve directory specifiers — it needs an explicit `./hooks/index`. This pass
7
+ // rewrites `./X` -> `./X/index` only when X resolves to a sibling DIRECTORY,
8
+ // leaving file specifiers (and ones already ending in /index) untouched.
9
+ //
10
+ // Pure-ish: operates on a real directory tree (so it's exercised with a temp
11
+ // fixture in tests) and is idempotent — safe to re-run.
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.normalizeSdkBarrels = normalizeSdkBarrels;
17
+ const node_fs_1 = __importDefault(require("node:fs"));
18
+ const node_path_1 = __importDefault(require("node:path"));
19
+ const SPEC_RE = /((?:export|import)[^\n]*?from\s*["'])(\.\.?\/[^"']+?)(["'])/g;
20
+ const FILE_EXT_RE = /\.(tsx?|jsx?|json|css)$/;
21
+ /** Rewrite directory barrel specifiers under `root` in place. Returns the count
22
+ * of files changed. No-op (returns 0) when `root` does not exist. */
23
+ function normalizeSdkBarrels(root) {
24
+ if (!node_fs_1.default.existsSync(root))
25
+ return 0;
26
+ let changed = 0;
27
+ const walk = (dir) => {
28
+ for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
29
+ const p = node_path_1.default.join(dir, entry.name);
30
+ if (entry.isDirectory()) {
31
+ walk(p);
32
+ continue;
33
+ }
34
+ if (!/\.tsx?$/.test(entry.name))
35
+ continue;
36
+ const orig = node_fs_1.default.readFileSync(p, 'utf8');
37
+ const next = orig.replace(SPEC_RE, (match, pre, spec, post) => {
38
+ if (FILE_EXT_RE.test(spec))
39
+ return match; // already a file
40
+ const abs = node_path_1.default.resolve(node_path_1.default.dirname(p), spec);
41
+ if (node_fs_1.default.existsSync(abs) && node_fs_1.default.statSync(abs).isDirectory()) {
42
+ return pre + spec.replace(/\/$/, '') + '/index' + post;
43
+ }
44
+ return match;
45
+ });
46
+ if (next !== orig) {
47
+ node_fs_1.default.writeFileSync(p, next);
48
+ changed++;
49
+ }
50
+ }
51
+ };
52
+ walk(root);
53
+ return changed;
54
+ }
@@ -0,0 +1,8 @@
1
+ export type PlaneEndpoints = {
2
+ admin: string;
3
+ auth: string;
4
+ app: string;
5
+ };
6
+ export declare function derivePlaneEndpoints(apiEndpoint: string, databaseName: string): PlaneEndpoints | undefined;
7
+ export declare function codegenEndpointEnv(planes: PlaneEndpoints): Record<string, string>;
8
+ export declare function envLocalContent(databaseName: string, planes: PlaneEndpoints | undefined): string;