@agentic-kit/pi 0.17.1 → 0.18.1
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
package/context.js
DELETED
|
@@ -1,282 +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.CONTEXT_ENV_PREFIX = exports.CONTEXT_ENV_KEYS = void 0;
|
|
7
|
-
exports.fromEnvironment = fromEnvironment;
|
|
8
|
-
exports.fromEnvFile = fromEnvFile;
|
|
9
|
-
exports.resolveProjectContext = resolveProjectContext;
|
|
10
|
-
exports.resolveDataToken = resolveDataToken;
|
|
11
|
-
exports.deriveSubdomainEndpoint = deriveSubdomainEndpoint;
|
|
12
|
-
exports.resolveOrgName = resolveOrgName;
|
|
13
|
-
const promises_1 = require("node:fs/promises");
|
|
14
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
-
const sdk_1 = require("@constructive-io/sdk");
|
|
16
|
-
const dotenv_1 = require("12factor-env/dotenv");
|
|
17
|
-
const db_probe_1 = require("./db-probe");
|
|
18
|
-
const host_1 = require("./host");
|
|
19
|
-
const DEFAULT_API_ENDPOINT = 'http://api.localhost:3000/graphql';
|
|
20
|
-
const DEFAULT_MODULES_ENDPOINT = 'http://modules.localhost:3000/graphql';
|
|
21
|
-
// The project values pi needs, and where they come from. A scaffolded project
|
|
22
|
-
// folder carries them in `.env` (desktop); a headless host — a container, a
|
|
23
|
-
// Job, CI — injects them as real environment variables, so no credential is
|
|
24
|
-
// ever written into a git clone the agent could commit. Both lanes produce the
|
|
25
|
-
// same record, which is why the resolver takes VALUES, not a directory.
|
|
26
|
-
exports.CONTEXT_ENV_KEYS = [
|
|
27
|
-
'ACCESS_TOKEN',
|
|
28
|
-
'DATABASE_ID',
|
|
29
|
-
'API_ENDPOINT',
|
|
30
|
-
'MODULES_ENDPOINT',
|
|
31
|
-
'DATABASE_NAME',
|
|
32
|
-
'OWNER_ID',
|
|
33
|
-
];
|
|
34
|
-
/** Prefix for injected variables: `ACCESS_TOKEN` is far too generic to claim in
|
|
35
|
-
* a shared process environment, `CONSTRUCTIVE_ACCESS_TOKEN` is not. Inside a
|
|
36
|
-
* project `.env` the bare names stay readable (and are what the scaffolder
|
|
37
|
-
* writes), so both spellings resolve, prefixed winning. */
|
|
38
|
-
exports.CONTEXT_ENV_PREFIX = 'CONSTRUCTIVE_';
|
|
39
|
-
const lookup = (source) => typeof source === 'function' ? source : (name) => source[name];
|
|
40
|
-
const readContextValue = (source, key) => {
|
|
41
|
-
const get = lookup(source);
|
|
42
|
-
const value = get(`${exports.CONTEXT_ENV_PREFIX}${key}`) ?? get(key);
|
|
43
|
-
return value ? value : undefined;
|
|
44
|
-
};
|
|
45
|
-
/** Use the process environment (or any injected record) as the source. */
|
|
46
|
-
function fromEnvironment(environment = process.env) {
|
|
47
|
-
return environment;
|
|
48
|
-
}
|
|
49
|
-
/** Read a project `.env` as the source. The file is authoritative here — the
|
|
50
|
-
* key in it belongs to whatever backend wrote it, so it is not merged under
|
|
51
|
-
* the ambient environment. Returns null when the file is absent. */
|
|
52
|
-
async function fromEnvFile(cwd) {
|
|
53
|
-
try {
|
|
54
|
-
return (0, dotenv_1.parseDotenv)(await (0, promises_1.readFile)(node_path_1.default.join(cwd, '.env'), 'utf8'));
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Resolve the project context from injected values, or from a project folder.
|
|
62
|
-
*
|
|
63
|
-
* Passing a `cwd` string keeps the original behavior (read `<cwd>/.env`) so
|
|
64
|
-
* existing hosts — Constructive Desktop, the confirm gate — are unchanged.
|
|
65
|
-
* Headless hosts pass values instead: `fromEnvironment()` for a container or
|
|
66
|
-
* Job, an explicit record for anything else.
|
|
67
|
-
*/
|
|
68
|
-
async function resolveProjectContext(input, options = {}) {
|
|
69
|
-
const source = typeof input === 'string' ? await fromEnvFile(input) : input;
|
|
70
|
-
if (!source) {
|
|
71
|
-
return {
|
|
72
|
-
context: null,
|
|
73
|
-
reason: 'No .env found in the project. Provision a Constructive database first (scaffold the project), then retry.',
|
|
74
|
-
code: 'no-env',
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
const env = Object.fromEntries(exports.CONTEXT_ENV_KEYS.map((key) => [key, readContextValue(source, key)]));
|
|
78
|
-
const accessToken = env.ACCESS_TOKEN;
|
|
79
|
-
const databaseId = env.DATABASE_ID;
|
|
80
|
-
if (!accessToken || !databaseId) {
|
|
81
|
-
return {
|
|
82
|
-
context: null,
|
|
83
|
-
reason: `Project is not connected to a Constructive database yet (missing ${exports.CONTEXT_ENV_PREFIX}ACCESS_TOKEN/${exports.CONTEXT_ENV_PREFIX}DATABASE_ID in the environment, or ACCESS_TOKEN/DATABASE_ID in .env). Provision the database first, then retry.`,
|
|
84
|
-
code: 'missing-credentials',
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
// A source-supplied pin wins for the DATA plane (the access key belongs to
|
|
88
|
-
// whatever backend wrote it); otherwise fall back to the app's backend-config
|
|
89
|
-
// store (environment-aware) so app + harness share one endpoint source.
|
|
90
|
-
const host = (0, host_1.getHost)();
|
|
91
|
-
const backend = host.backendConfig();
|
|
92
|
-
const apiEndpoint = env.API_ENDPOINT || backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
|
|
93
|
-
const modulesEndpoint = env.MODULES_ENDPOINT || backend?.modulesEndpoint || DEFAULT_MODULES_ENDPOINT;
|
|
94
|
-
// The whole control plane (binding probe, schema resolution, blueprint/schema
|
|
95
|
-
// tools) authenticates with the ACCOUNT bearer: the platform api rejects
|
|
96
|
-
// per-database keys, so the project ACCESS_TOKEN can never act on metaschema
|
|
97
|
-
// surfaces. The source's key stays in the context for the data plane only.
|
|
98
|
-
// The bearer is only ever sent to the app-configured backend — never a
|
|
99
|
-
// source-pinned endpoint, which an untrusted cloned project controls.
|
|
100
|
-
const controlApiEndpoint = backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
|
|
101
|
-
const controlModulesEndpoint = backend?.modulesEndpoint || DEFAULT_MODULES_ENDPOINT;
|
|
102
|
-
const account = host.account();
|
|
103
|
-
const accountBearer = account?.apiKey ?? account?.accessToken;
|
|
104
|
-
if (!accountBearer) {
|
|
105
|
-
return {
|
|
106
|
-
context: null,
|
|
107
|
-
reason: `No usable account credential to reach the Constructive control plane. ${host.signInHint ?? 'Sign in to the app, then retry.'}`,
|
|
108
|
-
code: 'missing-credentials',
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
const controlHeaders = { Authorization: `Bearer ${accountBearer}` };
|
|
112
|
-
const apiClient = sdk_1.api.createClient({ endpoint: controlApiEndpoint, headers: controlHeaders });
|
|
113
|
-
// Always probe the bound database — the DATABASE_ID stamp proves it WAS
|
|
114
|
-
// bound, never that the database still exists (backend refresh, deletion,
|
|
115
|
-
// revoked key). The probe is the single source of binding health.
|
|
116
|
-
const probe = await (0, db_probe_1.probeDatabase)({
|
|
117
|
-
endpoint: controlApiEndpoint,
|
|
118
|
-
bearer: accountBearer,
|
|
119
|
-
databaseId,
|
|
120
|
-
signInHint: host.signInHint,
|
|
121
|
-
});
|
|
122
|
-
if (probe.outcome === 'unreachable') {
|
|
123
|
-
return {
|
|
124
|
-
context: null,
|
|
125
|
-
reason: `Could not reach the Constructive backend at ${controlApiEndpoint} (${probe.detail}). Check it is running, then retry.`,
|
|
126
|
-
code: 'backend-unreachable',
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
if (probe.outcome === 'missing') {
|
|
130
|
-
return {
|
|
131
|
-
context: null,
|
|
132
|
-
reason: `The bound database (DATABASE_ID=${databaseId}) no longer exists on this backend. Re-provision with provision_database (reprovision: true) — the schema is rebuilt from the project; records do not carry over.`,
|
|
133
|
-
code: 'db-missing',
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
// Schemas are account-scoped: a live binding under a different account is
|
|
137
|
-
// never shown in the Schemas tab or written to by the agent. The project's
|
|
138
|
-
// local blueprint survives account changes, so recovery is a reprovision
|
|
139
|
-
// under the signed-in account. Owner truth comes from the probe (backend),
|
|
140
|
-
// not the source's stamp. Data-plane resolution skips this gate — see
|
|
141
|
-
// ResolveOptions.
|
|
142
|
-
const sessionUserId = account?.userId;
|
|
143
|
-
if (options.plane !== 'data' &&
|
|
144
|
-
probe.ownerId &&
|
|
145
|
-
sessionUserId &&
|
|
146
|
-
probe.ownerId !== sessionUserId) {
|
|
147
|
-
return {
|
|
148
|
-
context: null,
|
|
149
|
-
reason: `The bound database (DATABASE_ID=${databaseId}) was provisioned under a different account than the one signed in. Re-provision with provision_database (reprovision: true) to rebuild it under this account — the schema is rebuilt from the project; records do not carry over.`,
|
|
150
|
-
code: 'account-mismatch',
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
const databaseName = env.DATABASE_NAME || probe.name || '';
|
|
154
|
-
const ownerId = probe.ownerId || env.OWNER_ID || undefined;
|
|
155
|
-
const dataEndpoint = databaseName
|
|
156
|
-
? deriveSubdomainEndpoint(apiEndpoint, `api-${databaseName}`)
|
|
157
|
-
: '';
|
|
158
|
-
const schemaId = await resolveSchemaId(apiClient, databaseId);
|
|
159
|
-
if (!schemaId) {
|
|
160
|
-
return {
|
|
161
|
-
context: null,
|
|
162
|
-
reason: 'Could not resolve a schema (app_public/public) for this database. The database may not be fully provisioned yet.',
|
|
163
|
-
code: 'schema-unresolved',
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
return {
|
|
167
|
-
context: {
|
|
168
|
-
api: apiClient,
|
|
169
|
-
modules: sdk_1.modules.createClient({ endpoint: controlModulesEndpoint, headers: controlHeaders }),
|
|
170
|
-
databaseId,
|
|
171
|
-
schemaId,
|
|
172
|
-
ownerId,
|
|
173
|
-
apiEndpoint,
|
|
174
|
-
modulesEndpoint,
|
|
175
|
-
databaseName,
|
|
176
|
-
accessToken,
|
|
177
|
-
dataEndpoint,
|
|
178
|
-
},
|
|
179
|
-
reason: '',
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
const needsAuthReason = () => `Not signed in to the app database yet. ${(0, host_1.getHost)().signInHint ??
|
|
183
|
-
'Sign in from the Sheets tab, or open the Preview and sign in to your app, then try again.'}`;
|
|
184
|
-
// Resolve a data-plane token for record operations: serve the broker's active
|
|
185
|
-
// account while valid, otherwise harvest the token the user created by signing
|
|
186
|
-
// into their app in the Preview and adopt it into the broker. Also returns the
|
|
187
|
-
// signed-in user's id (used to scope rows by entityId). Returns a reason (no
|
|
188
|
-
// token) when neither source has a valid token — the caller surfaces a sign-in
|
|
189
|
-
// prompt.
|
|
190
|
-
async function resolveDataToken(context) {
|
|
191
|
-
const host = (0, host_1.getHost)();
|
|
192
|
-
const broker = host.dataAuthBroker;
|
|
193
|
-
const active = broker?.getActiveToken(context.databaseId);
|
|
194
|
-
if (active)
|
|
195
|
-
return { token: active.token, userId: active.userId };
|
|
196
|
-
const preview = host.previewToken ? await host.previewToken() : null;
|
|
197
|
-
if (!preview)
|
|
198
|
-
return { reason: needsAuthReason() };
|
|
199
|
-
if (broker?.isInvalidToken(context.databaseId, preview.accessToken)) {
|
|
200
|
-
return { reason: needsAuthReason() };
|
|
201
|
-
}
|
|
202
|
-
const parsed = preview.accessTokenExpiresAt ? Date.parse(preview.accessTokenExpiresAt) : NaN;
|
|
203
|
-
const expiresAt = Number.isNaN(parsed) ? Date.now() + 60 * 60 * 1000 : parsed;
|
|
204
|
-
const skewMs = host.dataTokenSkewMs ?? host_1.DEFAULT_DATA_TOKEN_SKEW_MS;
|
|
205
|
-
if (expiresAt <= Date.now() + skewMs)
|
|
206
|
-
return { reason: needsAuthReason() };
|
|
207
|
-
broker?.adoptToken(context.databaseId, {
|
|
208
|
-
userId: preview.userId,
|
|
209
|
-
token: preview.accessToken,
|
|
210
|
-
expiresAt,
|
|
211
|
-
origin: 'harvest',
|
|
212
|
-
});
|
|
213
|
-
return { token: preview.accessToken, userId: preview.userId };
|
|
214
|
-
}
|
|
215
|
-
// Per-DB endpoints are deterministic on the provisioning subdomain (= DATABASE_NAME):
|
|
216
|
-
// each plane swaps the first host label of its control-plane endpoint
|
|
217
|
-
// (e.g. api.localhost -> api-myapp.localhost, auth.localhost -> auth-myapp.localhost).
|
|
218
|
-
function deriveSubdomainEndpoint(baseEndpoint, firstLabel) {
|
|
219
|
-
try {
|
|
220
|
-
const url = new URL(baseEndpoint);
|
|
221
|
-
const labels = url.hostname.split('.');
|
|
222
|
-
labels[0] = firstLabel;
|
|
223
|
-
url.hostname = labels.join('.');
|
|
224
|
-
return url.toString();
|
|
225
|
-
}
|
|
226
|
-
catch {
|
|
227
|
-
return '';
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// The backend hierarchy is org -> db -> tables; the owning org is the auth-plane
|
|
231
|
-
// user the database's ownerId points at. Best-effort display lookup only —
|
|
232
|
-
// binding health never depends on it. Authenticates with the account bearer
|
|
233
|
-
// against the app-configured auth endpoint (the platform rejects per-database
|
|
234
|
-
// keys, and the bearer never goes to a .env-pinned endpoint).
|
|
235
|
-
async function resolveOrgName(ownerId) {
|
|
236
|
-
try {
|
|
237
|
-
const host = (0, host_1.getHost)();
|
|
238
|
-
const account = host.account();
|
|
239
|
-
const bearer = account?.apiKey ?? account?.accessToken;
|
|
240
|
-
if (!bearer)
|
|
241
|
-
return undefined;
|
|
242
|
-
const backend = host.backendConfig();
|
|
243
|
-
const authEndpoint = deriveSubdomainEndpoint(backend?.apiEndpoint || DEFAULT_API_ENDPOINT, 'auth');
|
|
244
|
-
if (!authEndpoint)
|
|
245
|
-
return undefined;
|
|
246
|
-
const client = sdk_1.auth.createClient({
|
|
247
|
-
endpoint: authEndpoint,
|
|
248
|
-
headers: { Authorization: `Bearer ${bearer}` },
|
|
249
|
-
});
|
|
250
|
-
const result = await client.user
|
|
251
|
-
.findMany({
|
|
252
|
-
select: { displayName: true, username: true },
|
|
253
|
-
where: { id: { equalTo: ownerId } },
|
|
254
|
-
})
|
|
255
|
-
.execute();
|
|
256
|
-
if (!result.ok)
|
|
257
|
-
return undefined;
|
|
258
|
-
const node = (result.data.users?.nodes ?? [])[0];
|
|
259
|
-
return node?.displayName || node?.username || undefined;
|
|
260
|
-
}
|
|
261
|
-
catch {
|
|
262
|
-
return undefined;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
async function resolveSchemaId(apiClient, databaseId) {
|
|
266
|
-
const result = await apiClient.schema
|
|
267
|
-
.findMany({
|
|
268
|
-
select: { id: true, name: true },
|
|
269
|
-
where: { databaseId: { equalTo: databaseId } },
|
|
270
|
-
})
|
|
271
|
-
.execute();
|
|
272
|
-
if (!result.ok)
|
|
273
|
-
return undefined;
|
|
274
|
-
const nodes = (result.data.schemas?.nodes ?? []).filter((s) => Boolean(s && s.id));
|
|
275
|
-
const appPublic = nodes.find((s) => s.name === 'app_public');
|
|
276
|
-
if (appPublic)
|
|
277
|
-
return appPublic.id;
|
|
278
|
-
const pub = nodes.find((s) => s.name === 'public');
|
|
279
|
-
if (pub)
|
|
280
|
-
return pub.id;
|
|
281
|
-
return nodes[0]?.id;
|
|
282
|
-
}
|
package/db-probe.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export type DatabaseProbe = {
|
|
2
|
-
outcome: 'found';
|
|
3
|
-
name?: string;
|
|
4
|
-
ownerId?: string;
|
|
5
|
-
} | {
|
|
6
|
-
outcome: 'missing';
|
|
7
|
-
} | {
|
|
8
|
-
outcome: 'unreachable';
|
|
9
|
-
detail: string;
|
|
10
|
-
};
|
|
11
|
-
export type ProbeExecutor = {
|
|
12
|
-
execute<T>(document: string, variables?: Record<string, unknown>): Promise<{
|
|
13
|
-
ok: true;
|
|
14
|
-
data: T;
|
|
15
|
-
errors: undefined;
|
|
16
|
-
} | {
|
|
17
|
-
ok: false;
|
|
18
|
-
data: null;
|
|
19
|
-
errors: {
|
|
20
|
-
message: string;
|
|
21
|
-
}[];
|
|
22
|
-
}>;
|
|
23
|
-
};
|
|
24
|
-
export declare function probeDatabase(args: {
|
|
25
|
-
endpoint: string;
|
|
26
|
-
bearer: string;
|
|
27
|
-
databaseId: string;
|
|
28
|
-
executor?: ProbeExecutor;
|
|
29
|
-
signInHint?: string;
|
|
30
|
-
}): Promise<DatabaseProbe>;
|
package/db-probe.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.probeDatabase = probeDatabase;
|
|
4
|
-
const sdk_1 = require("@constructive-io/sdk");
|
|
5
|
-
const AUTH_ERROR_RE = /unauthenticated|unauthorized|permission denied|jwt|http 401|http 403/i;
|
|
6
|
-
// Liveness probe for a bound database. The split drives recovery: 'missing'
|
|
7
|
-
// means the backend answered and the binding is dead (database deleted,
|
|
8
|
-
// backend refreshed) — recovery is reprovision. 'unreachable' means no usable
|
|
9
|
-
// answer (backend down, network error, rejected credential) — recovery is
|
|
10
|
-
// retry/re-sign-in, never reprovision.
|
|
11
|
-
//
|
|
12
|
-
// The probe authenticates with the ACCOUNT bearer, not the project's .env key:
|
|
13
|
-
// the platform api rejects per-database keys, and the singular `database(id:)`
|
|
14
|
-
// field is gone from the current contract, so the lookup goes through the
|
|
15
|
-
// `databases` collection filter via the SDK's raw FetchAdapter.
|
|
16
|
-
async function probeDatabase(args) {
|
|
17
|
-
const executor = args.executor ??
|
|
18
|
-
new sdk_1.api.FetchAdapter(args.endpoint, { Authorization: `Bearer ${args.bearer}` });
|
|
19
|
-
const document = `
|
|
20
|
-
query ProbeDatabase {
|
|
21
|
-
databases(where: { id: { equalTo: ${JSON.stringify(args.databaseId)} } }) {
|
|
22
|
-
nodes { id name ownerId }
|
|
23
|
-
}
|
|
24
|
-
}`;
|
|
25
|
-
try {
|
|
26
|
-
const result = await executor.execute(document);
|
|
27
|
-
if (result.ok) {
|
|
28
|
-
const node = result.data.databases?.nodes?.[0];
|
|
29
|
-
if (!node)
|
|
30
|
-
return { outcome: 'missing' };
|
|
31
|
-
return { outcome: 'found', name: node.name ?? undefined, ownerId: node.ownerId ?? undefined };
|
|
32
|
-
}
|
|
33
|
-
const detail = result.errors?.[0]?.message ?? 'unknown error';
|
|
34
|
-
// A 5xx is the backend failing, not an answer about this database. An auth
|
|
35
|
-
// rejection is a dead ACCOUNT credential — never evidence the binding is
|
|
36
|
-
// dead, so it must not push toward reprovision.
|
|
37
|
-
if (/^HTTP 5\d\d/i.test(detail))
|
|
38
|
-
return { outcome: 'unreachable', detail };
|
|
39
|
-
if (AUTH_ERROR_RE.test(detail)) {
|
|
40
|
-
return {
|
|
41
|
-
outcome: 'unreachable',
|
|
42
|
-
detail: `${detail} — the account credential was rejected. ${args.signInHint ?? 'Sign in again, then retry.'}`,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
return { outcome: 'missing' };
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
return { outcome: 'unreachable', detail: err instanceof Error ? err.message : String(err) };
|
|
49
|
-
}
|
|
50
|
-
}
|
package/esm/app-workspace.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export declare const NATIVE_BUILD_DEPS: string[];
|
|
2
|
-
export declare const IGNORED_BUILDS_RE: RegExp;
|
|
3
|
-
export declare function run(cmd: string, args: string[], cwd: string, env?: Record<string, string>): Promise<{
|
|
4
|
-
ok: boolean;
|
|
5
|
-
out: string;
|
|
6
|
-
}>;
|
|
7
|
-
export declare function ensureWorkspaceConfig(cwd: string): Promise<void>;
|
|
8
|
-
export declare function pinCodegenLatest(appDir: string): Promise<void>;
|
|
9
|
-
export declare function ensureAppScaffold(cwd: string): Promise<{
|
|
10
|
-
ok: boolean;
|
|
11
|
-
out: string;
|
|
12
|
-
}>;
|
|
13
|
-
export declare function prewarmAppWorkspace(cwd: string): Promise<{
|
|
14
|
-
ok: boolean;
|
|
15
|
-
out: string;
|
|
16
|
-
}>;
|
package/esm/app-workspace.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { promisify } from 'node:util';
|
|
7
|
-
const execFileAsync = promisify(execFile);
|
|
8
|
-
// pnpm 11.8 no longer reads the `pnpm` field from a non-root package.json, so the
|
|
9
|
-
// template's `pnpm.overrides.graphql` (in packages/app/package.json) is silently
|
|
10
|
-
// ignored. Without that override two graphql versions (16.13.0 + 16.14.0) get
|
|
11
|
-
// installed, which instantiates two copies of `grafast` under DIFFERENT
|
|
12
|
-
// peer-dependency hash contexts. graphile-search then resolves in one context
|
|
13
|
-
// while graphile-connection-filter was linked under the other, so codegen dies
|
|
14
|
-
// with `Cannot find module 'graphile-connection-filter'`. Hoisting the override
|
|
15
|
-
// to the ROOT pnpm-workspace.yaml (which pnpm 11.8 DOES read) collapses graphql —
|
|
16
|
-
// and therefore grafast — to a single version, and the missing module resolves
|
|
17
|
-
// (PNPM-GRAPHQL-OVERRIDE-001). We also strip the nested packages/app
|
|
18
|
-
// pnpm-workspace.yaml the template ships, and keep onlyBuiltDependencies so the
|
|
19
|
-
// native deps build where honored (PNPM-BUILDS-001).
|
|
20
|
-
export const NATIVE_BUILD_DEPS = ['esbuild', 'sharp', 'unrs-resolver'];
|
|
21
|
-
export const IGNORED_BUILDS_RE = /ERR_PNPM_IGNORED_BUILDS|Ignored build scripts/;
|
|
22
|
-
const DEFAULT_GRAPHQL_OVERRIDE = '16.14.0';
|
|
23
|
-
const TEMPLATE_REPO = 'https://github.com/constructive-io/sandbox-templates';
|
|
24
|
-
const TEMPLATE_SUBDIR = path.join('nextjs', 'constructive-app');
|
|
25
|
-
export async function run(cmd, args, cwd, env) {
|
|
26
|
-
try {
|
|
27
|
-
const { stdout, stderr } = await execFileAsync(cmd, args, {
|
|
28
|
-
cwd,
|
|
29
|
-
timeout: 5 * 60_000,
|
|
30
|
-
encoding: 'utf-8',
|
|
31
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
32
|
-
env: env ? { ...process.env, ...env } : process.env,
|
|
33
|
-
});
|
|
34
|
-
return { ok: true, out: `${stdout}${stderr}`.trim() };
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
const e = err;
|
|
38
|
-
return { ok: false, out: `${e.stdout ?? ''}${e.stderr ?? ''}`.trim() || e.message || 'failed' };
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Read the graphql pin the template wants from its (now-ignored) pnpm.overrides
|
|
42
|
-
// so we propagate the template's own value instead of hardcoding ours.
|
|
43
|
-
async function templateGraphqlOverride(cwd) {
|
|
44
|
-
try {
|
|
45
|
-
const pkg = JSON.parse(await readFile(path.join(cwd, 'packages', 'app', 'package.json'), 'utf8'));
|
|
46
|
-
const v = pkg?.pnpm?.overrides?.graphql;
|
|
47
|
-
return typeof v === 'string' && v.length > 0 ? v : DEFAULT_GRAPHQL_OVERRIDE;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return DEFAULT_GRAPHQL_OVERRIDE;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
export async function ensureWorkspaceConfig(cwd) {
|
|
54
|
-
const nested = path.join(cwd, 'packages', 'app', 'pnpm-workspace.yaml');
|
|
55
|
-
if (existsSync(nested)) {
|
|
56
|
-
await rm(nested, { force: true });
|
|
57
|
-
}
|
|
58
|
-
const graphqlPin = await templateGraphqlOverride(cwd);
|
|
59
|
-
const builds = NATIVE_BUILD_DEPS.map((d) => ` - ${d}`).join('\n');
|
|
60
|
-
const text = `packages:\n - "packages/*"\n\n` +
|
|
61
|
-
`overrides:\n graphql: ${graphqlPin}\n\n` +
|
|
62
|
-
`onlyBuiltDependencies:\n${builds}\n`;
|
|
63
|
-
await writeFile(path.join(cwd, 'pnpm-workspace.yaml'), text);
|
|
64
|
-
}
|
|
65
|
-
// Force @constructive-io/graphql-codegen to `latest` in the app package.json
|
|
66
|
-
// before the single install — older 4.9.x throws on PostGraphile `filter` inputs
|
|
67
|
-
// (TS-001).
|
|
68
|
-
export async function pinCodegenLatest(appDir) {
|
|
69
|
-
const pkgPath = path.join(appDir, 'package.json');
|
|
70
|
-
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
71
|
-
for (const field of ['devDependencies', 'dependencies']) {
|
|
72
|
-
if (pkg[field]?.['@constructive-io/graphql-codegen']) {
|
|
73
|
-
pkg[field]['@constructive-io/graphql-codegen'] = 'latest';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
77
|
-
}
|
|
78
|
-
// The Constructive Next.js app is just the `nextjs/constructive-app` tree from
|
|
79
|
-
// the sandbox-templates repo — it ships no `____VAR____` placeholders, so there
|
|
80
|
-
// is nothing to templatize. `pgpm init` only fetches and copies that tree, but
|
|
81
|
-
// its CLI is unreliable in the non-TTY agent shell, which made the agent thrash
|
|
82
|
-
// for minutes. Materialize `packages/app` deterministically by shallow-cloning
|
|
83
|
-
// the template instead (PGPM-001).
|
|
84
|
-
export async function ensureAppScaffold(cwd) {
|
|
85
|
-
const appDir = path.join(cwd, 'packages', 'app');
|
|
86
|
-
if (existsSync(path.join(appDir, 'package.json'))) {
|
|
87
|
-
return { ok: true, out: 'packages/app already scaffolded' };
|
|
88
|
-
}
|
|
89
|
-
const tmp = path.join(os.tmpdir(), `airpage-app-template-${process.pid}`);
|
|
90
|
-
await rm(tmp, { recursive: true, force: true });
|
|
91
|
-
const clone = await run('git', ['clone', '--depth', '1', TEMPLATE_REPO, tmp], cwd);
|
|
92
|
-
if (!clone.ok) {
|
|
93
|
-
await rm(tmp, { recursive: true, force: true });
|
|
94
|
-
return { ok: false, out: `Template clone failed:\n${clone.out}` };
|
|
95
|
-
}
|
|
96
|
-
const src = path.join(tmp, TEMPLATE_SUBDIR);
|
|
97
|
-
if (!existsSync(src)) {
|
|
98
|
-
await rm(tmp, { recursive: true, force: true });
|
|
99
|
-
return { ok: false, out: `Template subdir ${TEMPLATE_SUBDIR} missing in ${TEMPLATE_REPO}` };
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
await mkdir(path.dirname(appDir), { recursive: true });
|
|
103
|
-
await cp(src, appDir, { recursive: true });
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
await rm(tmp, { recursive: true, force: true });
|
|
107
|
-
return {
|
|
108
|
-
ok: false,
|
|
109
|
-
out: `Copying template into packages/app failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
await rm(tmp, { recursive: true, force: true });
|
|
113
|
-
return { ok: true, out: 'Scaffolded packages/app from the constructive-app template.' };
|
|
114
|
-
}
|
|
115
|
-
// Scaffold packages/app, hoist the workspace config, pin codegen, and run the one
|
|
116
|
-
// clean install. All of this is database-INDEPENDENT, so it can be prewarmed
|
|
117
|
-
// concurrently with the ~90s provision_database backend mutation to keep clone +
|
|
118
|
-
// install off run_codegen's critical path. Best-effort and fully idempotent:
|
|
119
|
-
// run_codegen re-runs the same steps, finding the scaffold present and the install
|
|
120
|
-
// already satisfied (a fast no-op). Errors are swallowed by callers that prewarm —
|
|
121
|
-
// run_codegen surfaces any real failure later.
|
|
122
|
-
export async function prewarmAppWorkspace(cwd) {
|
|
123
|
-
const scaffold = await ensureAppScaffold(cwd);
|
|
124
|
-
if (!scaffold.ok)
|
|
125
|
-
return scaffold;
|
|
126
|
-
const appDir = path.join(cwd, 'packages', 'app');
|
|
127
|
-
await ensureWorkspaceConfig(cwd);
|
|
128
|
-
await pinCodegenLatest(appDir);
|
|
129
|
-
const install = await run('pnpm', ['install', '--no-frozen-lockfile'], cwd);
|
|
130
|
-
if (!install.ok && !IGNORED_BUILDS_RE.test(install.out)) {
|
|
131
|
-
return { ok: false, out: `pnpm install failed:\n${install.out}` };
|
|
132
|
-
}
|
|
133
|
-
return { ok: true, out: 'Prewarmed packages/app scaffold + install.' };
|
|
134
|
-
}
|
package/esm/context.d.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { api, modules } from '@constructive-io/sdk';
|
|
2
|
-
export type ApiClient = ReturnType<typeof api.createClient>;
|
|
3
|
-
export type ModulesClient = ReturnType<typeof modules.createClient>;
|
|
4
|
-
export type ProjectContext = {
|
|
5
|
-
api: ApiClient;
|
|
6
|
-
modules: ModulesClient;
|
|
7
|
-
databaseId: string;
|
|
8
|
-
schemaId: string;
|
|
9
|
-
ownerId: string | undefined;
|
|
10
|
-
apiEndpoint: string;
|
|
11
|
-
modulesEndpoint: string;
|
|
12
|
-
databaseName: string;
|
|
13
|
-
accessToken: string;
|
|
14
|
-
dataEndpoint: string;
|
|
15
|
-
};
|
|
16
|
-
export type ProjectContextFailureCode = 'no-env' | 'missing-credentials' | 'db-missing' | 'account-mismatch' | 'backend-unreachable' | 'schema-unresolved';
|
|
17
|
-
export type ResolveResult = {
|
|
18
|
-
context: ProjectContext | null;
|
|
19
|
-
reason: string;
|
|
20
|
-
code?: ProjectContextFailureCode;
|
|
21
|
-
};
|
|
22
|
-
export declare const CONTEXT_ENV_KEYS: readonly ["ACCESS_TOKEN", "DATABASE_ID", "API_ENDPOINT", "MODULES_ENDPOINT", "DATABASE_NAME", "OWNER_ID"];
|
|
23
|
-
export type ContextEnvKey = (typeof CONTEXT_ENV_KEYS)[number];
|
|
24
|
-
/** Prefix for injected variables: `ACCESS_TOKEN` is far too generic to claim in
|
|
25
|
-
* a shared process environment, `CONSTRUCTIVE_ACCESS_TOKEN` is not. Inside a
|
|
26
|
-
* project `.env` the bare names stay readable (and are what the scaffolder
|
|
27
|
-
* writes), so both spellings resolve, prefixed winning. */
|
|
28
|
-
export declare const CONTEXT_ENV_PREFIX = "CONSTRUCTIVE_";
|
|
29
|
-
/** Values keyed by name, or a lookup function (a host with a secret store). */
|
|
30
|
-
export type ContextSource = Record<string, string | undefined> | ((name: string) => string | undefined);
|
|
31
|
-
/** Use the process environment (or any injected record) as the source. */
|
|
32
|
-
export declare function fromEnvironment(environment?: Record<string, string | undefined>): ContextSource;
|
|
33
|
-
/** Read a project `.env` as the source. The file is authoritative here — the
|
|
34
|
-
* key in it belongs to whatever backend wrote it, so it is not merged under
|
|
35
|
-
* the ambient environment. Returns null when the file is absent. */
|
|
36
|
-
export declare function fromEnvFile(cwd: string): Promise<ContextSource | null>;
|
|
37
|
-
export type ResolveOptions = {
|
|
38
|
-
/** Which plane the context is for. Ownership gates the control plane only:
|
|
39
|
-
* schema editing and provisioning act under the signed-in account, so a
|
|
40
|
-
* foreign-owned binding is blocked ('account-mismatch'). The data plane
|
|
41
|
-
* (real rows via app end-user tokens) never checks desktop-account
|
|
42
|
-
* ownership — a live foreign-owned database serves data fine, exactly as
|
|
43
|
-
* the app itself does in the Preview. */
|
|
44
|
-
plane?: 'control' | 'data';
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* Resolve the project context from injected values, or from a project folder.
|
|
48
|
-
*
|
|
49
|
-
* Passing a `cwd` string keeps the original behavior (read `<cwd>/.env`) so
|
|
50
|
-
* existing hosts — Constructive Desktop, the confirm gate — are unchanged.
|
|
51
|
-
* Headless hosts pass values instead: `fromEnvironment()` for a container or
|
|
52
|
-
* Job, an explicit record for anything else.
|
|
53
|
-
*/
|
|
54
|
-
export declare function resolveProjectContext(input: string | ContextSource, options?: ResolveOptions): Promise<ResolveResult>;
|
|
55
|
-
export type DataTokenResult = {
|
|
56
|
-
token?: string;
|
|
57
|
-
userId?: string;
|
|
58
|
-
reason?: string;
|
|
59
|
-
};
|
|
60
|
-
export declare function resolveDataToken(context: ProjectContext): Promise<DataTokenResult>;
|
|
61
|
-
export declare function deriveSubdomainEndpoint(baseEndpoint: string, firstLabel: string): string;
|
|
62
|
-
export declare function resolveOrgName(ownerId: string): Promise<string | undefined>;
|