@agentic-kit/pi 0.17.0 → 0.18.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/README.md +5 -7
- package/confirm-gate.d.ts +1 -2
- package/esm/confirm-gate.d.ts +1 -2
- package/esm/host.d.ts +5 -110
- package/esm/host.js +5 -18
- package/esm/index.d.ts +17 -9
- package/esm/index.js +20 -38
- package/esm/pi-tool.d.ts +16 -0
- package/esm/pi-tool.js +32 -0
- package/host.d.ts +5 -110
- package/host.js +10 -22
- package/index.d.ts +17 -9
- package/index.js +47 -62
- package/package.json +6 -10
- package/pi-tool.d.ts +16 -0
- package/pi-tool.js +36 -0
- package/app-workspace.d.ts +0 -16
- package/app-workspace.js +0 -145
- package/context.d.ts +0 -62
- package/context.js +0 -282
- package/db-probe.d.ts +0 -30
- package/db-probe.js +0 -50
- package/esm/app-workspace.d.ts +0 -16
- package/esm/app-workspace.js +0 -134
- package/esm/context.d.ts +0 -62
- package/esm/context.js +0 -270
- package/esm/db-probe.d.ts +0 -30
- package/esm/db-probe.js +0 -47
- package/esm/policy/add-policies-to-table.d.ts +0 -26
- package/esm/policy/add-policies-to-table.js +0 -66
- package/esm/policy/provision-helpers.d.ts +0 -38
- package/esm/policy/provision-helpers.js +0 -50
- package/esm/provision-database/credential.d.ts +0 -14
- package/esm/provision-database/credential.js +0 -19
- package/esm/provision-database/env-file.d.ts +0 -37
- package/esm/provision-database/env-file.js +0 -85
- package/esm/provision-database/manifest.d.ts +0 -10
- package/esm/provision-database/manifest.js +0 -48
- package/esm/provision-database/pg-fixups.d.ts +0 -16
- package/esm/provision-database/pg-fixups.js +0 -132
- package/esm/provision-database/preset-match.d.ts +0 -13
- package/esm/provision-database/preset-match.js +0 -66
- package/esm/provision-database/presets.d.ts +0 -4
- package/esm/provision-database/presets.js +0 -13
- package/esm/provision-database/request-database.d.ts +0 -61
- package/esm/provision-database/request-database.js +0 -130
- package/esm/provision-database/resolve.d.ts +0 -27
- package/esm/provision-database/resolve.js +0 -49
- package/esm/records/meta.d.ts +0 -41
- package/esm/records/meta.js +0 -167
- package/esm/run-codegen/barrels.d.ts +0 -3
- package/esm/run-codegen/barrels.js +0 -48
- package/esm/run-codegen/endpoints.d.ts +0 -8
- package/esm/run-codegen/endpoints.js +0 -46
- package/esm/schema-resolve.d.ts +0 -23
- package/esm/schema-resolve.js +0 -69
- package/esm/tool-schema.d.ts +0 -3
- package/esm/tool-schema.js +0 -10
- package/esm/tools/add-policies.d.ts +0 -8
- package/esm/tools/add-policies.js +0 -96
- package/esm/tools/add-records.d.ts +0 -14
- package/esm/tools/add-records.js +0 -124
- package/esm/tools/add-relation-schema.d.ts +0 -30
- package/esm/tools/add-relation-schema.js +0 -58
- package/esm/tools/add-relation.d.ts +0 -10
- package/esm/tools/add-relation.js +0 -91
- package/esm/tools/create-api-key.d.ts +0 -18
- package/esm/tools/create-api-key.js +0 -216
- package/esm/tools/describe-schema.d.ts +0 -18
- package/esm/tools/describe-schema.js +0 -132
- package/esm/tools/manage-entity-types.d.ts +0 -41
- package/esm/tools/manage-entity-types.js +0 -203
- package/esm/tools/mutations.d.ts +0 -14
- package/esm/tools/mutations.js +0 -235
- package/esm/tools/provision-blueprint.d.ts +0 -22
- package/esm/tools/provision-blueprint.js +0 -111
- package/esm/tools/provision-database.d.ts +0 -14
- package/esm/tools/provision-database.js +0 -262
- package/esm/tools/run-codegen.d.ts +0 -11
- package/esm/tools/run-codegen.js +0 -139
- package/esm/tools/templates.d.ts +0 -49
- package/esm/tools/templates.js +0 -337
- package/policy/add-policies-to-table.d.ts +0 -26
- package/policy/add-policies-to-table.js +0 -71
- package/policy/provision-helpers.d.ts +0 -38
- package/policy/provision-helpers.js +0 -58
- package/provision-database/credential.d.ts +0 -14
- package/provision-database/credential.js +0 -22
- package/provision-database/env-file.d.ts +0 -37
- package/provision-database/env-file.js +0 -91
- package/provision-database/manifest.d.ts +0 -10
- package/provision-database/manifest.js +0 -56
- package/provision-database/pg-fixups.d.ts +0 -16
- package/provision-database/pg-fixups.js +0 -170
- package/provision-database/preset-match.d.ts +0 -13
- package/provision-database/preset-match.js +0 -70
- package/provision-database/presets.d.ts +0 -4
- package/provision-database/presets.js +0 -17
- package/provision-database/request-database.d.ts +0 -61
- package/provision-database/request-database.js +0 -134
- package/provision-database/resolve.d.ts +0 -27
- package/provision-database/resolve.js +0 -53
- package/records/meta.d.ts +0 -41
- package/records/meta.js +0 -172
- package/run-codegen/barrels.d.ts +0 -3
- package/run-codegen/barrels.js +0 -54
- package/run-codegen/endpoints.d.ts +0 -8
- package/run-codegen/endpoints.js +0 -51
- package/schema-resolve.d.ts +0 -23
- package/schema-resolve.js +0 -75
- package/tool-schema.d.ts +0 -3
- package/tool-schema.js +0 -13
- package/tools/add-policies.d.ts +0 -8
- package/tools/add-policies.js +0 -99
- package/tools/add-records.d.ts +0 -14
- package/tools/add-records.js +0 -127
- package/tools/add-relation-schema.d.ts +0 -30
- package/tools/add-relation-schema.js +0 -62
- package/tools/add-relation.d.ts +0 -10
- package/tools/add-relation.js +0 -94
- package/tools/create-api-key.d.ts +0 -18
- package/tools/create-api-key.js +0 -222
- package/tools/describe-schema.d.ts +0 -18
- package/tools/describe-schema.js +0 -135
- package/tools/manage-entity-types.d.ts +0 -41
- package/tools/manage-entity-types.js +0 -208
- package/tools/mutations.d.ts +0 -14
- package/tools/mutations.js +0 -238
- package/tools/provision-blueprint.d.ts +0 -22
- package/tools/provision-blueprint.js +0 -114
- package/tools/provision-database.d.ts +0 -14
- package/tools/provision-database.js +0 -268
- package/tools/run-codegen.d.ts +0 -11
- package/tools/run-codegen.js +0 -143
- package/tools/templates.d.ts +0 -49
- package/tools/templates.js +0 -341
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Pure .env merge — used by provision_database to persist provisioning
|
|
3
|
-
// credentials without clobbering existing keys: replace a key in place if
|
|
4
|
-
// present, else append.
|
|
5
|
-
// Kept side-effect-free so it is unit-testable (the tool does the fs write).
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.ARCHIVED_BINDING_KEYS = exports.PROVISION_ENV_KEYS = void 0;
|
|
8
|
-
exports.mergeEnv = mergeEnv;
|
|
9
|
-
exports.provisionEnvVars = provisionEnvVars;
|
|
10
|
-
exports.archiveBindingKeys = archiveBindingKeys;
|
|
11
|
-
/**
|
|
12
|
-
* Upsert `vars` into the existing `.env` `content`. Existing keys are replaced
|
|
13
|
-
* in place (preserving surrounding lines/comments); new keys are appended.
|
|
14
|
-
* Returns the new file body, trailing-newline-terminated.
|
|
15
|
-
*/
|
|
16
|
-
function mergeEnv(content, vars) {
|
|
17
|
-
let out = content;
|
|
18
|
-
for (const [key, val] of Object.entries(vars)) {
|
|
19
|
-
// Escape the key for the regex, and use a replacement function so `$`
|
|
20
|
-
// sequences in the value are never treated as replacement specials.
|
|
21
|
-
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
-
const regex = new RegExp(`^${escapedKey}=.*`, 'm');
|
|
23
|
-
if (regex.test(out))
|
|
24
|
-
out = out.replace(regex, () => `${key}=${val}`);
|
|
25
|
-
else
|
|
26
|
-
out += `\n${key}=${val}`;
|
|
27
|
-
}
|
|
28
|
-
return out.trim() + '\n';
|
|
29
|
-
}
|
|
30
|
-
/** The credential keys provision_database owns in a project's .env. */
|
|
31
|
-
exports.PROVISION_ENV_KEYS = [
|
|
32
|
-
'DATABASE_ID',
|
|
33
|
-
'DATABASE_NAME',
|
|
34
|
-
'OWNER_ID',
|
|
35
|
-
'ACCESS_TOKEN',
|
|
36
|
-
'PGDATABASE',
|
|
37
|
-
'API_ENDPOINT',
|
|
38
|
-
'MODULES_ENDPOINT',
|
|
39
|
-
];
|
|
40
|
-
/**
|
|
41
|
-
* Keys archived as comments on reprovision — the binding identity: which
|
|
42
|
-
* database, under whom, on which backend. ACCESS_TOKEN is the account login
|
|
43
|
-
* token, identical across bindings, so it is overwritten in place rather than
|
|
44
|
-
* archived.
|
|
45
|
-
*/
|
|
46
|
-
exports.ARCHIVED_BINDING_KEYS = [
|
|
47
|
-
'DATABASE_ID',
|
|
48
|
-
'DATABASE_NAME',
|
|
49
|
-
'OWNER_ID',
|
|
50
|
-
'PGDATABASE',
|
|
51
|
-
'API_ENDPOINT',
|
|
52
|
-
'MODULES_ENDPOINT',
|
|
53
|
-
];
|
|
54
|
-
/**
|
|
55
|
-
* Build the provisioning credential block written to a project's .env. The
|
|
56
|
-
* endpoint pins record which backend owns this binding — outside-app consumers
|
|
57
|
-
* (verify-feature.sh, skill scripts, dev-server shells) resolve them instead of
|
|
58
|
-
* falling back to localhost defaults when the app targets a hosted backend.
|
|
59
|
-
*/
|
|
60
|
-
function provisionEnvVars(args) {
|
|
61
|
-
return {
|
|
62
|
-
DATABASE_ID: args.databaseId,
|
|
63
|
-
DATABASE_NAME: args.databaseName,
|
|
64
|
-
OWNER_ID: args.ownerId,
|
|
65
|
-
ACCESS_TOKEN: args.accessToken,
|
|
66
|
-
PGDATABASE: args.databaseName,
|
|
67
|
-
API_ENDPOINT: args.apiEndpoint,
|
|
68
|
-
MODULES_ENDPOINT: args.modulesEndpoint,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Comment out the given keys in place (`# archived <date>: KEY=value`) so a
|
|
73
|
-
* reprovision keeps the previous binding readable in the file instead of
|
|
74
|
-
* silently overwriting it. Every other line — including existing comments and
|
|
75
|
-
* already-archived entries — passes through untouched.
|
|
76
|
-
*/
|
|
77
|
-
function archiveBindingKeys(content, keys, archivedOn) {
|
|
78
|
-
const keySet = new Set(keys);
|
|
79
|
-
return content
|
|
80
|
-
.split('\n')
|
|
81
|
-
.map((line) => {
|
|
82
|
-
if (line.trimStart().startsWith('#'))
|
|
83
|
-
return line;
|
|
84
|
-
const eq = line.indexOf('=');
|
|
85
|
-
if (eq === -1)
|
|
86
|
-
return line;
|
|
87
|
-
const key = line.slice(0, eq).trim();
|
|
88
|
-
return keySet.has(key) ? `# archived ${archivedOn}: ${line.trim()}` : line;
|
|
89
|
-
})
|
|
90
|
-
.join('\n');
|
|
91
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ProvisionOverlay } from './resolve';
|
|
2
|
-
export declare const PROVISION_MANIFEST_FILE = "provision.json";
|
|
3
|
-
export type ProvisionManifest = ProvisionOverlay;
|
|
4
|
-
/** Validate an untrusted value as a provision manifest, throwing on bad shape. */
|
|
5
|
-
export declare function parseProvisionManifest(value: unknown): ProvisionManifest;
|
|
6
|
-
/**
|
|
7
|
-
* Load `<dir>/provision.json` as an overlay, or `null` when absent. A malformed
|
|
8
|
-
* file throws so a broken overlay is surfaced rather than silently ignored.
|
|
9
|
-
*/
|
|
10
|
-
export declare function loadProvisionManifest(dir: string): Promise<ProvisionManifest | null>;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PROVISION_MANIFEST_FILE = void 0;
|
|
7
|
-
exports.parseProvisionManifest = parseProvisionManifest;
|
|
8
|
-
exports.loadProvisionManifest = loadProvisionManifest;
|
|
9
|
-
// `provision.json` overlay manifest.
|
|
10
|
-
//
|
|
11
|
-
// A pure-data overlay, distributed the same way skills are: written into a
|
|
12
|
-
// config/app directory (locally, or materialized there from appstash / a pinned
|
|
13
|
-
// git ref) and read at provision time. It NEVER installs or executes a package
|
|
14
|
-
// — the base preset is a pinned build-time dependency; this file only selects a
|
|
15
|
-
// preset and layers add/remove on top.
|
|
16
|
-
const promises_1 = require("node:fs/promises");
|
|
17
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
-
const zod_1 = require("zod");
|
|
19
|
-
exports.PROVISION_MANIFEST_FILE = 'provision.json';
|
|
20
|
-
const ModuleSchema = zod_1.z.union([
|
|
21
|
-
zod_1.z.string(),
|
|
22
|
-
zod_1.z.tuple([zod_1.z.string(), zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())]),
|
|
23
|
-
]);
|
|
24
|
-
const ProvisionManifestSchema = zod_1.z
|
|
25
|
-
.object({
|
|
26
|
-
preset: zod_1.z.string().optional(),
|
|
27
|
-
add: zod_1.z.array(ModuleSchema).optional(),
|
|
28
|
-
remove: zod_1.z.array(zod_1.z.string()).optional(),
|
|
29
|
-
})
|
|
30
|
-
.strict();
|
|
31
|
-
/** Validate an untrusted value as a provision manifest, throwing on bad shape. */
|
|
32
|
-
function parseProvisionManifest(value) {
|
|
33
|
-
return ProvisionManifestSchema.parse(value);
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Load `<dir>/provision.json` as an overlay, or `null` when absent. A malformed
|
|
37
|
-
* file throws so a broken overlay is surfaced rather than silently ignored.
|
|
38
|
-
*/
|
|
39
|
-
async function loadProvisionManifest(dir) {
|
|
40
|
-
const file = node_path_1.default.join(dir, exports.PROVISION_MANIFEST_FILE);
|
|
41
|
-
let raw;
|
|
42
|
-
try {
|
|
43
|
-
raw = await (0, promises_1.readFile)(file, 'utf8');
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
let parsed;
|
|
49
|
-
try {
|
|
50
|
-
parsed = JSON.parse(raw);
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
throw new Error(`Invalid ${exports.PROVISION_MANIFEST_FILE} at ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
54
|
-
}
|
|
55
|
-
return parseProvisionManifest(parsed);
|
|
56
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type FixupResult = {
|
|
2
|
-
applied: boolean;
|
|
3
|
-
note: string;
|
|
4
|
-
};
|
|
5
|
-
export type PgEnv = {
|
|
6
|
-
host?: string;
|
|
7
|
-
port?: string;
|
|
8
|
-
user?: string;
|
|
9
|
-
password?: string;
|
|
10
|
-
};
|
|
11
|
-
export declare function parsePgpmEnv(stdout: string): PgEnv;
|
|
12
|
-
export declare function resolvePgEnv(): Promise<PgEnv | null>;
|
|
13
|
-
export declare function applySqlFixups(args: {
|
|
14
|
-
databaseName: string;
|
|
15
|
-
physicalDb: string;
|
|
16
|
-
}): Promise<FixupResult>;
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Post-provision SQL fixups for a freshly provisioned database.
|
|
3
|
-
//
|
|
4
|
-
// Two things must happen at the Postgres level that the provisioning GraphQL API
|
|
5
|
-
// does not do for us:
|
|
6
|
-
//
|
|
7
|
-
// 1. Naming settings — `constructive.simple_schema_names` and
|
|
8
|
-
// `schema_use_underscores` make later `provision_blueprint` calls create an
|
|
9
|
-
// `app_public` schema (which resolveSchemaId looks for) instead of a
|
|
10
|
-
// hyphenated `<db>-<hex>-app-public` one. These ALTER DATABASE settings
|
|
11
|
-
// PERSIST, so we set them once here, before any blueprint construction.
|
|
12
|
-
//
|
|
13
|
-
// 2. Membership defaults — new sign-ups otherwise land with
|
|
14
|
-
// is_approved=false/is_verified=false, the AuthzEntityMembership policy then
|
|
15
|
-
// denies every insert/select, and CRUD rows silently never persist
|
|
16
|
-
// (BLUEPRINT-PENDING-001). We flip the defaults (future sign-ups) AND any
|
|
17
|
-
// memberships already created during provisioning.
|
|
18
|
-
//
|
|
19
|
-
// All SQL targets the PHYSICAL control-plane database (`constructive`), NOT the
|
|
20
|
-
// app's database name — that name is only a schema prefix (MEMBERSHIP-DB-001).
|
|
21
|
-
//
|
|
22
|
-
// Connection creds come from the ambient PG* env vars, falling back to sourcing
|
|
23
|
-
// `pgpm env` (the same mechanism the old `eval "$(pgpm env)"` shell step used).
|
|
24
|
-
// If neither yields a connection, provisioning still succeeded — we return a
|
|
25
|
-
// note so the tool can warn the user that sign-in/CRUD may need a manual fixup.
|
|
26
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
27
|
-
if (k2 === undefined) k2 = k;
|
|
28
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
29
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
30
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
31
|
-
}
|
|
32
|
-
Object.defineProperty(o, k2, desc);
|
|
33
|
-
}) : (function(o, m, k, k2) {
|
|
34
|
-
if (k2 === undefined) k2 = k;
|
|
35
|
-
o[k2] = m[k];
|
|
36
|
-
}));
|
|
37
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
38
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
39
|
-
}) : function(o, v) {
|
|
40
|
-
o["default"] = v;
|
|
41
|
-
});
|
|
42
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
43
|
-
var ownKeys = function(o) {
|
|
44
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
45
|
-
var ar = [];
|
|
46
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
47
|
-
return ar;
|
|
48
|
-
};
|
|
49
|
-
return ownKeys(o);
|
|
50
|
-
};
|
|
51
|
-
return function (mod) {
|
|
52
|
-
if (mod && mod.__esModule) return mod;
|
|
53
|
-
var result = {};
|
|
54
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
55
|
-
__setModuleDefault(result, mod);
|
|
56
|
-
return result;
|
|
57
|
-
};
|
|
58
|
-
})();
|
|
59
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
-
exports.parsePgpmEnv = parsePgpmEnv;
|
|
61
|
-
exports.resolvePgEnv = resolvePgEnv;
|
|
62
|
-
exports.applySqlFixups = applySqlFixups;
|
|
63
|
-
const node_child_process_1 = require("node:child_process");
|
|
64
|
-
const node_util_1 = require("node:util");
|
|
65
|
-
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
66
|
-
// Parse `pgpm env` output. It prints shell assignments (optionally `export`-
|
|
67
|
-
// prefixed), one per line: `export PGHOST=localhost`. Quotes are stripped.
|
|
68
|
-
function parsePgpmEnv(stdout) {
|
|
69
|
-
const env = {};
|
|
70
|
-
for (const rawLine of stdout.split('\n')) {
|
|
71
|
-
const line = rawLine.trim().replace(/^export\s+/, '');
|
|
72
|
-
const eq = line.indexOf('=');
|
|
73
|
-
if (eq === -1)
|
|
74
|
-
continue;
|
|
75
|
-
const key = line.slice(0, eq).trim();
|
|
76
|
-
let value = line.slice(eq + 1).trim();
|
|
77
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
78
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
79
|
-
value = value.slice(1, -1);
|
|
80
|
-
}
|
|
81
|
-
env[key] = value;
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
host: env.PGHOST,
|
|
85
|
-
port: env.PGPORT,
|
|
86
|
-
user: env.PGUSER,
|
|
87
|
-
password: env.PGPASSWORD,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
async function resolvePgEnv() {
|
|
91
|
-
if (process.env.PGHOST) {
|
|
92
|
-
return {
|
|
93
|
-
host: process.env.PGHOST,
|
|
94
|
-
port: process.env.PGPORT,
|
|
95
|
-
user: process.env.PGUSER,
|
|
96
|
-
password: process.env.PGPASSWORD,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
const { stdout } = await execFileAsync('pgpm', ['env'], { timeout: 15_000 });
|
|
101
|
-
const parsed = parsePgpmEnv(stdout);
|
|
102
|
-
return parsed.host ? parsed : null;
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// Find the memberships schema for this app. Schema names use hyphens locally
|
|
109
|
-
// (`<db>-<hex>-memberships-public`) but may use underscores when the naming
|
|
110
|
-
// settings are active — match both, scoped to this app's name prefix.
|
|
111
|
-
const MEMBERSHIPS_SCHEMA_QUERY = `
|
|
112
|
-
SELECT schema_name FROM information_schema.schemata
|
|
113
|
-
WHERE (schema_name LIKE '%memberships-public' OR schema_name LIKE '%memberships_public')
|
|
114
|
-
AND (schema_name LIKE $1 OR schema_name LIKE $2)
|
|
115
|
-
ORDER BY schema_name DESC LIMIT 1`;
|
|
116
|
-
async function applySqlFixups(args) {
|
|
117
|
-
const pgEnv = await resolvePgEnv();
|
|
118
|
-
if (!pgEnv) {
|
|
119
|
-
return {
|
|
120
|
-
applied: false,
|
|
121
|
-
note: 'Could not reach Postgres (no PGHOST and `pgpm env` unavailable). Database provisioned, but membership defaults were not enabled — sign-in/CRUD may fail until the constructive DB is fixed up manually.',
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
// pg is dynamically imported so the main bundle never loads it unless a
|
|
125
|
-
// provision actually runs with Postgres access. The import lives inside the
|
|
126
|
-
// try: fixups are best-effort, so even a broken pg module must degrade to a
|
|
127
|
-
// note instead of failing the tool after the database is already provisioned.
|
|
128
|
-
let pool;
|
|
129
|
-
try {
|
|
130
|
-
const { Pool } = await Promise.resolve().then(() => __importStar(require('pg')));
|
|
131
|
-
pool = new Pool({
|
|
132
|
-
host: pgEnv.host,
|
|
133
|
-
port: pgEnv.port ? Number(pgEnv.port) : undefined,
|
|
134
|
-
user: pgEnv.user,
|
|
135
|
-
password: pgEnv.password,
|
|
136
|
-
database: args.physicalDb,
|
|
137
|
-
});
|
|
138
|
-
const db = args.physicalDb;
|
|
139
|
-
// Persistent naming settings — so future provision_blueprint calls land an
|
|
140
|
-
// `app_public` schema resolveSchemaId can find.
|
|
141
|
-
await pool.query(`ALTER DATABASE "${db}" SET constructive.simple_schema_names = 'true'`);
|
|
142
|
-
await pool.query(`ALTER DATABASE "${db}" SET constructive.schema_use_underscores = 'true'`);
|
|
143
|
-
const schemaResult = await pool.query(MEMBERSHIPS_SCHEMA_QUERY, [
|
|
144
|
-
`${args.databaseName}-%`,
|
|
145
|
-
`${args.databaseName}\\_%`,
|
|
146
|
-
]);
|
|
147
|
-
if (schemaResult.rows.length === 0) {
|
|
148
|
-
return {
|
|
149
|
-
applied: true,
|
|
150
|
-
note: `Naming settings applied, but no memberships schema was found for "${args.databaseName}" — sign-in may need a manual membership fixup.`,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
const membershipsSchema = schemaResult.rows[0].schema_name;
|
|
154
|
-
await pool.query(`UPDATE "${membershipsSchema}".app_membership_defaults SET is_approved = TRUE, is_verified = TRUE`);
|
|
155
|
-
await pool.query(`UPDATE "${membershipsSchema}".app_memberships
|
|
156
|
-
SET is_approved = TRUE, is_verified = TRUE
|
|
157
|
-
WHERE is_approved = FALSE OR is_verified = FALSE`);
|
|
158
|
-
return { applied: true, note: `Membership defaults enabled (schema: ${membershipsSchema}).` };
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
162
|
-
return {
|
|
163
|
-
applied: false,
|
|
164
|
-
note: `Database provisioned, but SQL fixups failed (${message}). Sign-in/CRUD may fail until membership defaults are enabled manually.`,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
finally {
|
|
168
|
-
await pool?.end().catch(() => { });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type ProvisionModule } from './presets';
|
|
2
|
-
import type { ProvisionRequest } from './request-database';
|
|
3
|
-
/**
|
|
4
|
-
* The slug of the registry preset whose module set equals `resolved` under
|
|
5
|
-
* the backend's hash semantics, or undefined when no preset matches.
|
|
6
|
-
*/
|
|
7
|
-
export declare function matchPresetSlug(resolved: ProvisionModule[]): string | undefined;
|
|
8
|
-
/**
|
|
9
|
-
* Build the requestDatabase input branch: presetSlug when the resolved set
|
|
10
|
-
* equals a registry preset (warm-pool eligible), else the explicit modules
|
|
11
|
-
* array — never both.
|
|
12
|
-
*/
|
|
13
|
-
export declare function selectProvisionRequest(resolved: ProvisionModule[]): ProvisionRequest;
|
|
@@ -1,70 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
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 };
|
|
@@ -1,17 +0,0 @@
|
|
|
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;
|
|
@@ -1,61 +0,0 @@
|
|
|
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
|
-
}>;
|
|
@@ -1,134 +0,0 @@
|
|
|
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
|
-
}
|