@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.
- package/LICENSE +23 -0
- package/README.md +138 -0
- package/app-workspace.d.ts +16 -0
- package/app-workspace.js +145 -0
- package/context.d.ts +62 -0
- package/context.js +282 -0
- package/db-probe.d.ts +30 -0
- package/db-probe.js +50 -0
- package/esm/app-workspace.d.ts +16 -0
- package/esm/app-workspace.js +134 -0
- package/esm/context.d.ts +62 -0
- package/esm/context.js +270 -0
- package/esm/db-probe.d.ts +30 -0
- package/esm/db-probe.js +47 -0
- package/esm/host.d.ts +115 -0
- package/esm/host.js +23 -0
- package/esm/index.d.ts +20 -0
- package/esm/index.js +52 -0
- package/esm/policy/add-policies-to-table.d.ts +26 -0
- package/esm/policy/add-policies-to-table.js +66 -0
- package/esm/policy/provision-helpers.d.ts +38 -0
- package/esm/policy/provision-helpers.js +50 -0
- package/esm/provision-database/credential.d.ts +14 -0
- package/esm/provision-database/credential.js +19 -0
- package/esm/provision-database/env-file.d.ts +37 -0
- package/esm/provision-database/env-file.js +85 -0
- package/esm/provision-database/manifest.d.ts +10 -0
- package/esm/provision-database/manifest.js +48 -0
- package/esm/provision-database/pg-fixups.d.ts +16 -0
- package/esm/provision-database/pg-fixups.js +132 -0
- package/esm/provision-database/preset-match.d.ts +13 -0
- package/esm/provision-database/preset-match.js +66 -0
- package/esm/provision-database/presets.d.ts +4 -0
- package/esm/provision-database/presets.js +13 -0
- package/esm/provision-database/request-database.d.ts +61 -0
- package/esm/provision-database/request-database.js +130 -0
- package/esm/provision-database/resolve.d.ts +27 -0
- package/esm/provision-database/resolve.js +49 -0
- package/esm/records/meta.d.ts +41 -0
- package/esm/records/meta.js +167 -0
- package/esm/run-codegen/barrels.d.ts +3 -0
- package/esm/run-codegen/barrels.js +48 -0
- package/esm/run-codegen/endpoints.d.ts +8 -0
- package/esm/run-codegen/endpoints.js +46 -0
- package/esm/schema-resolve.d.ts +23 -0
- package/esm/schema-resolve.js +69 -0
- package/esm/tool-schema.d.ts +3 -0
- package/esm/tool-schema.js +10 -0
- package/esm/tools/add-policies.d.ts +24 -0
- package/esm/tools/add-policies.js +94 -0
- package/esm/tools/add-records.d.ts +18 -0
- package/esm/tools/add-records.js +122 -0
- package/esm/tools/add-relation-schema.d.ts +29 -0
- package/esm/tools/add-relation-schema.js +56 -0
- package/esm/tools/add-relation.d.ts +10 -0
- package/esm/tools/add-relation.js +91 -0
- package/esm/tools/create-api-key.d.ts +25 -0
- package/esm/tools/create-api-key.js +214 -0
- package/esm/tools/describe-schema.d.ts +19 -0
- package/esm/tools/describe-schema.js +130 -0
- package/esm/tools/manage-entity-types.d.ts +40 -0
- package/esm/tools/manage-entity-types.js +201 -0
- package/esm/tools/mutations.d.ts +35 -0
- package/esm/tools/mutations.js +230 -0
- package/esm/tools/provision-blueprint.d.ts +21 -0
- package/esm/tools/provision-blueprint.js +109 -0
- package/esm/tools/provision-database.d.ts +18 -0
- package/esm/tools/provision-database.js +260 -0
- package/esm/tools/run-codegen.d.ts +12 -0
- package/esm/tools/run-codegen.js +137 -0
- package/esm/tools/templates.d.ts +71 -0
- package/esm/tools/templates.js +331 -0
- package/host.d.ts +115 -0
- package/host.js +28 -0
- package/index.d.ts +20 -0
- package/index.js +76 -0
- package/package.json +49 -0
- package/policy/add-policies-to-table.d.ts +26 -0
- package/policy/add-policies-to-table.js +71 -0
- package/policy/provision-helpers.d.ts +38 -0
- package/policy/provision-helpers.js +58 -0
- package/provision-database/credential.d.ts +14 -0
- package/provision-database/credential.js +22 -0
- package/provision-database/env-file.d.ts +37 -0
- package/provision-database/env-file.js +91 -0
- package/provision-database/manifest.d.ts +10 -0
- package/provision-database/manifest.js +56 -0
- package/provision-database/pg-fixups.d.ts +16 -0
- package/provision-database/pg-fixups.js +170 -0
- package/provision-database/preset-match.d.ts +13 -0
- package/provision-database/preset-match.js +70 -0
- package/provision-database/presets.d.ts +4 -0
- package/provision-database/presets.js +17 -0
- package/provision-database/request-database.d.ts +61 -0
- package/provision-database/request-database.js +134 -0
- package/provision-database/resolve.d.ts +27 -0
- package/provision-database/resolve.js +53 -0
- package/records/meta.d.ts +41 -0
- package/records/meta.js +172 -0
- package/run-codegen/barrels.d.ts +3 -0
- package/run-codegen/barrels.js +54 -0
- package/run-codegen/endpoints.d.ts +8 -0
- package/run-codegen/endpoints.js +51 -0
- package/schema-resolve.d.ts +23 -0
- package/schema-resolve.js +75 -0
- package/tool-schema.d.ts +3 -0
- package/tool-schema.js +13 -0
- package/tools/add-policies.d.ts +24 -0
- package/tools/add-policies.js +97 -0
- package/tools/add-records.d.ts +18 -0
- package/tools/add-records.js +125 -0
- package/tools/add-relation-schema.d.ts +29 -0
- package/tools/add-relation-schema.js +60 -0
- package/tools/add-relation.d.ts +10 -0
- package/tools/add-relation.js +94 -0
- package/tools/create-api-key.d.ts +25 -0
- package/tools/create-api-key.js +220 -0
- package/tools/describe-schema.d.ts +19 -0
- package/tools/describe-schema.js +133 -0
- package/tools/manage-entity-types.d.ts +40 -0
- package/tools/manage-entity-types.js +206 -0
- package/tools/mutations.d.ts +35 -0
- package/tools/mutations.js +233 -0
- package/tools/provision-blueprint.d.ts +21 -0
- package/tools/provision-blueprint.js +112 -0
- package/tools/provision-database.d.ts +18 -0
- package/tools/provision-database.js +266 -0
- package/tools/run-codegen.d.ts +12 -0
- package/tools/run-codegen.js +141 -0
- package/tools/templates.d.ts +71 -0
- 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 {};
|
package/records/meta.js
ADDED
|
@@ -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,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;
|