@lotics/cli 0.71.0 → 0.74.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 +38 -0
- package/dist/app_commands.d.ts +13 -0
- package/dist/app_commands.js +3 -3
- package/dist/args.d.ts +17 -0
- package/dist/args.js +35 -2
- package/dist/args.test.js +49 -0
- package/dist/cli.js +235 -3
- package/dist/client.d.ts +321 -0
- package/dist/client.js +164 -0
- package/dist/dev/rpc_handler.d.ts +1 -1
- package/dist/dev/rpc_handler.js +10 -2
- package/dist/dev/rpc_handler.test.js +24 -6
- package/dist/generate_app_fields.d.ts +2 -0
- package/dist/generate_app_fields.js +1 -1
- package/dist/generate_package_fields.d.ts +48 -0
- package/dist/generate_package_fields.js +104 -0
- package/dist/generate_package_fields.test.d.ts +1 -0
- package/dist/generate_package_fields.test.js +51 -0
- package/dist/package_commands.d.ts +227 -0
- package/dist/package_commands.js +957 -0
- package/dist/package_commands.test.d.ts +1 -0
- package/dist/package_commands.test.js +303 -0
- package/dist/src/cli.js +1420 -138
- package/dist/starter_template.d.ts +1 -1
- package/dist/starter_template.js +10 -4
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -43,6 +43,14 @@ export interface WorkspaceInfo {
|
|
|
43
43
|
default_currency: string;
|
|
44
44
|
organization_id: string;
|
|
45
45
|
created_at: string;
|
|
46
|
+
/**
|
|
47
|
+
* Whether this is a throwaway app-package dev workspace (set with
|
|
48
|
+
* `lotics workspace create <name> --dev`). Gates the destructive package
|
|
49
|
+
* dev/sync scaffold + the server-side `package reset`. Optional because the
|
|
50
|
+
* list endpoint only carries it once the server serializes `is_dev`; an absent
|
|
51
|
+
* value is treated as non-dev by the guard (fail closed).
|
|
52
|
+
*/
|
|
53
|
+
is_dev?: boolean;
|
|
46
54
|
}
|
|
47
55
|
export interface ToolExecuteResult {
|
|
48
56
|
result: unknown;
|
|
@@ -65,6 +73,28 @@ export interface FileUploadResult {
|
|
|
65
73
|
error: string;
|
|
66
74
|
}>;
|
|
67
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* A materialized app package's binding — alias → this workspace's concrete id,
|
|
78
|
+
* one string-map per namespace. Mirrors the server's `bindingSchema`. The
|
|
79
|
+
* `workflows` map is the entity-lifecycle table-workflow ROW ledger (an artifact
|
|
80
|
+
* registry, not a schema binding), carried in the same shape. The CLI never
|
|
81
|
+
* interprets these maps — it round-trips them verbatim between `extract` (which
|
|
82
|
+
* emits the origin workspace's) and `adopt` (which replays it onto the app).
|
|
83
|
+
*/
|
|
84
|
+
export interface PackageBinding {
|
|
85
|
+
entities: Record<string, string>;
|
|
86
|
+
fields: Record<string, string>;
|
|
87
|
+
options: Record<string, string>;
|
|
88
|
+
templates: Record<string, string>;
|
|
89
|
+
roles: Record<string, string>;
|
|
90
|
+
workflows: Record<string, string>;
|
|
91
|
+
}
|
|
92
|
+
/** One finding from a bespoke→package extraction (`extractAppPackage`). */
|
|
93
|
+
export interface ExtractFinding {
|
|
94
|
+
severity: "error" | "warning" | "info";
|
|
95
|
+
area: string;
|
|
96
|
+
message: string;
|
|
97
|
+
}
|
|
68
98
|
export declare const API_BASE_URL: string;
|
|
69
99
|
export declare class LoticsClient {
|
|
70
100
|
private apiKey;
|
|
@@ -88,10 +118,19 @@ export declare class LoticsClient {
|
|
|
88
118
|
organization_name: string;
|
|
89
119
|
}>;
|
|
90
120
|
setWorkspaceId(id: string): void;
|
|
121
|
+
/** The workspace id the client targets (the `x-workspace-id` header), if resolved. */
|
|
122
|
+
getWorkspaceId(): string | undefined;
|
|
91
123
|
listWorkspaces(): Promise<WorkspaceInfo[]>;
|
|
124
|
+
/**
|
|
125
|
+
* Resolve one workspace's info by id from the org's workspace list (the only
|
|
126
|
+
* API-key-accessible source carrying `is_dev`). Returns null when the
|
|
127
|
+
* workspace isn't visible to these credentials.
|
|
128
|
+
*/
|
|
129
|
+
getWorkspaceInfo(id: string): Promise<WorkspaceInfo | null>;
|
|
92
130
|
createWorkspace(body: {
|
|
93
131
|
name: string;
|
|
94
132
|
timezone?: string;
|
|
133
|
+
is_dev?: boolean;
|
|
95
134
|
}): Promise<WorkspaceInfo>;
|
|
96
135
|
deleteWorkspace(id: string): Promise<{
|
|
97
136
|
id: string;
|
|
@@ -155,6 +194,12 @@ export declare class LoticsClient {
|
|
|
155
194
|
inputs?: Record<string, unknown>;
|
|
156
195
|
outputs?: Record<string, unknown>;
|
|
157
196
|
}> | null;
|
|
197
|
+
/**
|
|
198
|
+
* Installation-level customization config values (the `useConfig()`
|
|
199
|
+
* source). Null/undefined for a bespoke app. The dev harness serves these
|
|
200
|
+
* through the `context` op for production parity.
|
|
201
|
+
*/
|
|
202
|
+
config?: Record<string, string | number | boolean> | null;
|
|
158
203
|
}>;
|
|
159
204
|
createApp(body: {
|
|
160
205
|
name: string;
|
|
@@ -166,6 +211,276 @@ export declare class LoticsClient {
|
|
|
166
211
|
workspace_id: string;
|
|
167
212
|
current_version_id: string | null;
|
|
168
213
|
}>;
|
|
214
|
+
/**
|
|
215
|
+
* Install an app package version into the current workspace — scaffolds the
|
|
216
|
+
* data model, deploys the package bundle, materializes its
|
|
217
|
+
* queries/workflows/agents, and pins the installation. Returns the resulting
|
|
218
|
+
* installation app. `version` omitted installs the latest published version.
|
|
219
|
+
*/
|
|
220
|
+
installAppPackage(package_id: string, body: {
|
|
221
|
+
version?: number;
|
|
222
|
+
}): Promise<{
|
|
223
|
+
id: string;
|
|
224
|
+
name: string;
|
|
225
|
+
workspace_id: string;
|
|
226
|
+
package_id: string | null;
|
|
227
|
+
package_version: number | null;
|
|
228
|
+
current_version_id: string | null;
|
|
229
|
+
}>;
|
|
230
|
+
/**
|
|
231
|
+
* Eject an installation from its package — re-deploy the pinned version's
|
|
232
|
+
* source as a workspace-owned app version, then sever the package link
|
|
233
|
+
* (clears `package_id`/`package_version`, stamps `ejected_at`). One-way: the
|
|
234
|
+
* app becomes a normal bespoke app and can no longer be upgraded. Returns the
|
|
235
|
+
* resulting app.
|
|
236
|
+
*/
|
|
237
|
+
ejectAppPackage(app_id: string): Promise<{
|
|
238
|
+
id: string;
|
|
239
|
+
name: string;
|
|
240
|
+
workspace_id: string;
|
|
241
|
+
current_version_id: string | null;
|
|
242
|
+
}>;
|
|
243
|
+
/**
|
|
244
|
+
* Extract a DRAFT app package from an existing bespoke app — the promotion
|
|
245
|
+
* read (docs/app_packages.md § Promotion). Pure: nothing is written. Returns
|
|
246
|
+
* the alias-keyed draft `contract` (opaque to the CLI — the server is the
|
|
247
|
+
* validating authority), the origin workspace's `binding` (which doubles as
|
|
248
|
+
* the adopt binding), a findings `report` (any `error` ⇒ not publishable
|
|
249
|
+
* as-is), and the file-backed `template_files` the CLI must stage into the
|
|
250
|
+
* project at their `bytes_ref` paths. Backs `lotics package extract`.
|
|
251
|
+
* Admin-only.
|
|
252
|
+
*/
|
|
253
|
+
extractAppPackage(app_id: string): Promise<{
|
|
254
|
+
contract: unknown;
|
|
255
|
+
binding: PackageBinding;
|
|
256
|
+
report: ExtractFinding[];
|
|
257
|
+
template_files: Array<{
|
|
258
|
+
bytes_ref: string;
|
|
259
|
+
file_id: string;
|
|
260
|
+
filename: string;
|
|
261
|
+
}>;
|
|
262
|
+
}>;
|
|
263
|
+
/**
|
|
264
|
+
* Adopt a published package onto an EXISTING (bespoke or ejected) app — the
|
|
265
|
+
* final promotion step. The workspace already holds the concrete objects, so
|
|
266
|
+
* nothing is scaffolded or rewritten: the server verifies the `binding` is
|
|
267
|
+
* complete, live, and FAITHFUL to the version's contract, then writes only the
|
|
268
|
+
* installation pin (the app becomes installation #1, upgradeable again). A
|
|
269
|
+
* ConflictError names the aliases that diverge. `version` omitted adopts the
|
|
270
|
+
* latest. Backs `lotics package adopt`. Admin-only.
|
|
271
|
+
*/
|
|
272
|
+
adoptAppPackage(app_id: string, body: {
|
|
273
|
+
package_id: string;
|
|
274
|
+
version?: number;
|
|
275
|
+
binding: PackageBinding;
|
|
276
|
+
}): Promise<{
|
|
277
|
+
id: string;
|
|
278
|
+
name: string;
|
|
279
|
+
package_id: string | null;
|
|
280
|
+
package_version: number | null;
|
|
281
|
+
}>;
|
|
282
|
+
/**
|
|
283
|
+
* Fleet upgrade — bring every installation of a package across the CALLER'S
|
|
284
|
+
* org to the target version (latest when omitted) in one call. Hands-off
|
|
285
|
+
* applies only where the preview is clean; installations with breaking/
|
|
286
|
+
* drift/modified-core findings are skipped and reported for the normal
|
|
287
|
+
* per-installation consent flow. Backs `lotics package fleet-upgrade`.
|
|
288
|
+
* Admin-only; org-scoped (no workspace header needed).
|
|
289
|
+
*/
|
|
290
|
+
fleetUpgradeAppPackage(package_id: string, body: {
|
|
291
|
+
version?: number;
|
|
292
|
+
}): Promise<{
|
|
293
|
+
package_id: string;
|
|
294
|
+
target_version: number;
|
|
295
|
+
installations: Array<{
|
|
296
|
+
app_id: string;
|
|
297
|
+
app_name: string;
|
|
298
|
+
workspace_id: string;
|
|
299
|
+
workspace_name: string;
|
|
300
|
+
from_version: number | null;
|
|
301
|
+
outcome: "upgraded" | "up_to_date" | "skipped" | "failed";
|
|
302
|
+
blockers?: {
|
|
303
|
+
breaking: number;
|
|
304
|
+
drift: number;
|
|
305
|
+
modified: number;
|
|
306
|
+
};
|
|
307
|
+
message?: string;
|
|
308
|
+
}>;
|
|
309
|
+
}>;
|
|
310
|
+
/**
|
|
311
|
+
* Create a registry app package — the Lotics-owned, workspace-agnostic
|
|
312
|
+
* blueprint. `lotics package publish` calls this on first publish (when the
|
|
313
|
+
* local manifest has no `package_id`), then publishes version 1 against the
|
|
314
|
+
* returned id. Admin-only.
|
|
315
|
+
*/
|
|
316
|
+
createAppPackage(body: {
|
|
317
|
+
name: string;
|
|
318
|
+
description?: string | null;
|
|
319
|
+
}): Promise<{
|
|
320
|
+
id: string;
|
|
321
|
+
name: string;
|
|
322
|
+
description: string | null;
|
|
323
|
+
latest_version: number;
|
|
324
|
+
is_official: boolean;
|
|
325
|
+
created_at: string;
|
|
326
|
+
updated_at: string;
|
|
327
|
+
}>;
|
|
328
|
+
/**
|
|
329
|
+
* Fetch a registry app package's metadata (incl. `latest_version` and the
|
|
330
|
+
* Lotics-backed `is_official` trust badge). Admin-only; cross-tenant by id.
|
|
331
|
+
*/
|
|
332
|
+
getAppPackage(package_id: string): Promise<{
|
|
333
|
+
id: string;
|
|
334
|
+
name: string;
|
|
335
|
+
description: string | null;
|
|
336
|
+
latest_version: number;
|
|
337
|
+
is_official: boolean;
|
|
338
|
+
created_at: string;
|
|
339
|
+
updated_at: string;
|
|
340
|
+
}>;
|
|
341
|
+
/**
|
|
342
|
+
* Publish a new immutable package version — multipart upload of the alias-keyed
|
|
343
|
+
* contract (JSON) + the prebuilt code bundle (a gzipped tarball carrying
|
|
344
|
+
* `source.tar.gz` + `dist.tar.gz` members). The server validates the contract +
|
|
345
|
+
* bundle, then allocates the next monotonic version. The `contract` is opaque
|
|
346
|
+
* JSON to the transport (the server is the validating authority). Admin-only.
|
|
347
|
+
*/
|
|
348
|
+
publishAppPackageVersion(package_id: string, args: {
|
|
349
|
+
contract: unknown;
|
|
350
|
+
bundle: Buffer;
|
|
351
|
+
changelog?: string | null;
|
|
352
|
+
}): Promise<{
|
|
353
|
+
id: string;
|
|
354
|
+
package_id: string;
|
|
355
|
+
version: number;
|
|
356
|
+
bundle_r2_prefix: string;
|
|
357
|
+
changelog: string | null;
|
|
358
|
+
created_at: string;
|
|
359
|
+
}>;
|
|
360
|
+
/**
|
|
361
|
+
* Upgrade a package installation to a newer published version — extends the
|
|
362
|
+
* binding additively, re-materializes the target version's
|
|
363
|
+
* queries/workflows/agents (preserving workspace overlay), prunes dropped
|
|
364
|
+
* package artifacts, and bumps the pin. `version` omitted upgrades to the
|
|
365
|
+
* latest. Returns the resulting app. Admin-only.
|
|
366
|
+
*/
|
|
367
|
+
upgradeAppPackage(app_id: string, body: {
|
|
368
|
+
version?: number;
|
|
369
|
+
/**
|
|
370
|
+
* Per preview finding: drifted binding entries (`<namespace>.<alias>`)
|
|
371
|
+
* take `"recreate"` or `{ bind_to: "<id>" }`; locally modified
|
|
372
|
+
* artifacts (`<kind>.<alias>`) take `"revert"` or `"keep"`. Required
|
|
373
|
+
* for every finding — anything unresolved refuses the upgrade.
|
|
374
|
+
*/
|
|
375
|
+
resolutions?: Record<string, "recreate" | "revert" | "keep" | {
|
|
376
|
+
bind_to: string;
|
|
377
|
+
}>;
|
|
378
|
+
}): Promise<{
|
|
379
|
+
id: string;
|
|
380
|
+
name: string;
|
|
381
|
+
workspace_id: string;
|
|
382
|
+
package_id: string | null;
|
|
383
|
+
package_version: number | null;
|
|
384
|
+
current_version_id: string | null;
|
|
385
|
+
}>;
|
|
386
|
+
/**
|
|
387
|
+
* Preview a package upgrade — the additive plan, informational removals,
|
|
388
|
+
* breaking contract changes, the binding drift report, and the modified-core
|
|
389
|
+
* report — with no writes. Admin-only.
|
|
390
|
+
*/
|
|
391
|
+
previewAppPackageUpgrade(app_id: string, opts?: {
|
|
392
|
+
version?: number;
|
|
393
|
+
}): Promise<{
|
|
394
|
+
app_id: string;
|
|
395
|
+
package_id: string;
|
|
396
|
+
from_version: number;
|
|
397
|
+
to_version: number;
|
|
398
|
+
changelog: string | null;
|
|
399
|
+
diff: {
|
|
400
|
+
added: Record<string, unknown[]>;
|
|
401
|
+
removed: Record<string, unknown[]>;
|
|
402
|
+
breaking: Array<{
|
|
403
|
+
entity: string;
|
|
404
|
+
alias: string;
|
|
405
|
+
kind: "field_type" | "link_target";
|
|
406
|
+
from: string;
|
|
407
|
+
to: string;
|
|
408
|
+
}>;
|
|
409
|
+
};
|
|
410
|
+
drift: Array<{
|
|
411
|
+
namespace: string;
|
|
412
|
+
alias: string;
|
|
413
|
+
id: string;
|
|
414
|
+
}>;
|
|
415
|
+
modified: Array<{
|
|
416
|
+
kind: string;
|
|
417
|
+
alias: string;
|
|
418
|
+
}>;
|
|
419
|
+
}>;
|
|
420
|
+
/**
|
|
421
|
+
* Re-bind a package role to a different workspace group (the current group
|
|
422
|
+
* still exists). Re-materializes at the pinned version; refuses over
|
|
423
|
+
* modified-core findings. Admin-only.
|
|
424
|
+
*/
|
|
425
|
+
rebindAppPackageRole(app_id: string, body: {
|
|
426
|
+
role_alias: string;
|
|
427
|
+
group_id: string;
|
|
428
|
+
}): Promise<{
|
|
429
|
+
app_id: string;
|
|
430
|
+
role_alias: string;
|
|
431
|
+
group_id: string;
|
|
432
|
+
previous_group_id: string | null;
|
|
433
|
+
}>;
|
|
434
|
+
/**
|
|
435
|
+
* Workspace-wide dangling-reference sweep — active app/workflow artifacts
|
|
436
|
+
* whose prefixed schema ids no longer resolve. Backs
|
|
437
|
+
* `lotics workspace doctor`. Admin-only.
|
|
438
|
+
*/
|
|
439
|
+
getWorkspaceDanglingReferences(): Promise<Array<{
|
|
440
|
+
namespace: string;
|
|
441
|
+
id: string;
|
|
442
|
+
referent: {
|
|
443
|
+
kind: string;
|
|
444
|
+
id: string;
|
|
445
|
+
name: string;
|
|
446
|
+
};
|
|
447
|
+
}>>;
|
|
448
|
+
/**
|
|
449
|
+
* Health check for a package installation — version pin vs. registry latest,
|
|
450
|
+
* binding drift, and locally modified core artifacts. Read-only; backs
|
|
451
|
+
* `lotics package doctor`. Admin-only.
|
|
452
|
+
*/
|
|
453
|
+
getAppPackageHealth(app_id: string): Promise<{
|
|
454
|
+
app_id: string;
|
|
455
|
+
package_id: string;
|
|
456
|
+
package_name: string;
|
|
457
|
+
installed_version: number;
|
|
458
|
+
latest_version: number;
|
|
459
|
+
update_available: boolean;
|
|
460
|
+
drift: Array<{
|
|
461
|
+
namespace: string;
|
|
462
|
+
alias: string;
|
|
463
|
+
id: string;
|
|
464
|
+
}>;
|
|
465
|
+
modified: Array<{
|
|
466
|
+
kind: string;
|
|
467
|
+
alias: string;
|
|
468
|
+
}>;
|
|
469
|
+
}>;
|
|
470
|
+
/**
|
|
471
|
+
* Reset a package installation in a DEV workspace — drop the package-owned
|
|
472
|
+
* scaffolded tables and re-scaffold clean. Hard-gated server-side to dev
|
|
473
|
+
* workspaces (a non-dev workspace is refused). Returns the resulting app.
|
|
474
|
+
* Admin-only.
|
|
475
|
+
*/
|
|
476
|
+
resetAppPackage(app_id: string): Promise<{
|
|
477
|
+
id: string;
|
|
478
|
+
name: string;
|
|
479
|
+
workspace_id: string;
|
|
480
|
+
package_id: string | null;
|
|
481
|
+
package_version: number | null;
|
|
482
|
+
current_version_id: string | null;
|
|
483
|
+
}>;
|
|
169
484
|
/**
|
|
170
485
|
* Resolve the display name + fields (incl. select options) of the given tables
|
|
171
486
|
* — the schema `lotics app codegen` turns into the runtime `.lotics/app_fields.ts`
|
|
@@ -244,6 +559,12 @@ export declare class LoticsClient {
|
|
|
244
559
|
image: string | null;
|
|
245
560
|
}>;
|
|
246
561
|
}>;
|
|
562
|
+
/** A package installation's alias→id maps (fields/options/roles) — the app's runtime F/OPT resolution. */
|
|
563
|
+
appBinding(app_id: string): Promise<{
|
|
564
|
+
fields: Record<string, string>;
|
|
565
|
+
options: Record<string, string>;
|
|
566
|
+
roles: Record<string, string>;
|
|
567
|
+
}>;
|
|
247
568
|
/**
|
|
248
569
|
* Resolve the full option set (key, label, color) of a named query's select
|
|
249
570
|
* columns — the picker companion to `appQuery`. Mirrors
|
package/dist/client.js
CHANGED
|
@@ -136,9 +136,22 @@ export class LoticsClient {
|
|
|
136
136
|
setWorkspaceId(id) {
|
|
137
137
|
this.workspaceId = id;
|
|
138
138
|
}
|
|
139
|
+
/** The workspace id the client targets (the `x-workspace-id` header), if resolved. */
|
|
140
|
+
getWorkspaceId() {
|
|
141
|
+
return this.workspaceId;
|
|
142
|
+
}
|
|
139
143
|
async listWorkspaces() {
|
|
140
144
|
return this.request("GET", "/v1/workspaces");
|
|
141
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Resolve one workspace's info by id from the org's workspace list (the only
|
|
148
|
+
* API-key-accessible source carrying `is_dev`). Returns null when the
|
|
149
|
+
* workspace isn't visible to these credentials.
|
|
150
|
+
*/
|
|
151
|
+
async getWorkspaceInfo(id) {
|
|
152
|
+
const workspaces = await this.listWorkspaces();
|
|
153
|
+
return workspaces.find((w) => w.id === id) ?? null;
|
|
154
|
+
}
|
|
142
155
|
async createWorkspace(body) {
|
|
143
156
|
return this.request("POST", "/v1/workspaces", body);
|
|
144
157
|
}
|
|
@@ -190,6 +203,153 @@ export class LoticsClient {
|
|
|
190
203
|
async createApp(body) {
|
|
191
204
|
return this.request("POST", "/v1/apps", body);
|
|
192
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Install an app package version into the current workspace — scaffolds the
|
|
208
|
+
* data model, deploys the package bundle, materializes its
|
|
209
|
+
* queries/workflows/agents, and pins the installation. Returns the resulting
|
|
210
|
+
* installation app. `version` omitted installs the latest published version.
|
|
211
|
+
*/
|
|
212
|
+
async installAppPackage(package_id, body) {
|
|
213
|
+
return this.request("POST", `/v1/app-packages/${encodeURIComponent(package_id)}/installations`, body);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Eject an installation from its package — re-deploy the pinned version's
|
|
217
|
+
* source as a workspace-owned app version, then sever the package link
|
|
218
|
+
* (clears `package_id`/`package_version`, stamps `ejected_at`). One-way: the
|
|
219
|
+
* app becomes a normal bespoke app and can no longer be upgraded. Returns the
|
|
220
|
+
* resulting app.
|
|
221
|
+
*/
|
|
222
|
+
async ejectAppPackage(app_id) {
|
|
223
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/eject`);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Extract a DRAFT app package from an existing bespoke app — the promotion
|
|
227
|
+
* read (docs/app_packages.md § Promotion). Pure: nothing is written. Returns
|
|
228
|
+
* the alias-keyed draft `contract` (opaque to the CLI — the server is the
|
|
229
|
+
* validating authority), the origin workspace's `binding` (which doubles as
|
|
230
|
+
* the adopt binding), a findings `report` (any `error` ⇒ not publishable
|
|
231
|
+
* as-is), and the file-backed `template_files` the CLI must stage into the
|
|
232
|
+
* project at their `bytes_ref` paths. Backs `lotics package extract`.
|
|
233
|
+
* Admin-only.
|
|
234
|
+
*/
|
|
235
|
+
async extractAppPackage(app_id) {
|
|
236
|
+
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-extract`);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Adopt a published package onto an EXISTING (bespoke or ejected) app — the
|
|
240
|
+
* final promotion step. The workspace already holds the concrete objects, so
|
|
241
|
+
* nothing is scaffolded or rewritten: the server verifies the `binding` is
|
|
242
|
+
* complete, live, and FAITHFUL to the version's contract, then writes only the
|
|
243
|
+
* installation pin (the app becomes installation #1, upgradeable again). A
|
|
244
|
+
* ConflictError names the aliases that diverge. `version` omitted adopts the
|
|
245
|
+
* latest. Backs `lotics package adopt`. Admin-only.
|
|
246
|
+
*/
|
|
247
|
+
async adoptAppPackage(app_id, body) {
|
|
248
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-adopt`, body);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Fleet upgrade — bring every installation of a package across the CALLER'S
|
|
252
|
+
* org to the target version (latest when omitted) in one call. Hands-off
|
|
253
|
+
* applies only where the preview is clean; installations with breaking/
|
|
254
|
+
* drift/modified-core findings are skipped and reported for the normal
|
|
255
|
+
* per-installation consent flow. Backs `lotics package fleet-upgrade`.
|
|
256
|
+
* Admin-only; org-scoped (no workspace header needed).
|
|
257
|
+
*/
|
|
258
|
+
async fleetUpgradeAppPackage(package_id, body) {
|
|
259
|
+
return this.request("POST", `/v1/app-packages/${encodeURIComponent(package_id)}/fleet-upgrade`, body);
|
|
260
|
+
}
|
|
261
|
+
// --- App packages (registry authoring + dev harness) ---
|
|
262
|
+
/**
|
|
263
|
+
* Create a registry app package — the Lotics-owned, workspace-agnostic
|
|
264
|
+
* blueprint. `lotics package publish` calls this on first publish (when the
|
|
265
|
+
* local manifest has no `package_id`), then publishes version 1 against the
|
|
266
|
+
* returned id. Admin-only.
|
|
267
|
+
*/
|
|
268
|
+
async createAppPackage(body) {
|
|
269
|
+
return this.request("POST", "/v1/app-packages", body);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Fetch a registry app package's metadata (incl. `latest_version` and the
|
|
273
|
+
* Lotics-backed `is_official` trust badge). Admin-only; cross-tenant by id.
|
|
274
|
+
*/
|
|
275
|
+
async getAppPackage(package_id) {
|
|
276
|
+
return this.request("GET", `/v1/app-packages/${encodeURIComponent(package_id)}`);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Publish a new immutable package version — multipart upload of the alias-keyed
|
|
280
|
+
* contract (JSON) + the prebuilt code bundle (a gzipped tarball carrying
|
|
281
|
+
* `source.tar.gz` + `dist.tar.gz` members). The server validates the contract +
|
|
282
|
+
* bundle, then allocates the next monotonic version. The `contract` is opaque
|
|
283
|
+
* JSON to the transport (the server is the validating authority). Admin-only.
|
|
284
|
+
*/
|
|
285
|
+
async publishAppPackageVersion(package_id, args) {
|
|
286
|
+
const formData = new FormData();
|
|
287
|
+
formData.append("contract", JSON.stringify(args.contract));
|
|
288
|
+
formData.append("bundle", new Blob([new Uint8Array(args.bundle)], { type: "application/gzip" }), "bundle.tar.gz");
|
|
289
|
+
if (args.changelog)
|
|
290
|
+
formData.append("changelog", args.changelog);
|
|
291
|
+
const url = `${this.baseUrl}/v1/app-packages/${encodeURIComponent(package_id)}/versions`;
|
|
292
|
+
const response = await fetch(url, {
|
|
293
|
+
method: "POST",
|
|
294
|
+
headers: this.buildHeaders(), // no Content-Type — fetch sets the multipart boundary
|
|
295
|
+
body: formData,
|
|
296
|
+
});
|
|
297
|
+
if (!response.ok)
|
|
298
|
+
await this.throwResponseError(response);
|
|
299
|
+
return response.json();
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Upgrade a package installation to a newer published version — extends the
|
|
303
|
+
* binding additively, re-materializes the target version's
|
|
304
|
+
* queries/workflows/agents (preserving workspace overlay), prunes dropped
|
|
305
|
+
* package artifacts, and bumps the pin. `version` omitted upgrades to the
|
|
306
|
+
* latest. Returns the resulting app. Admin-only.
|
|
307
|
+
*/
|
|
308
|
+
async upgradeAppPackage(app_id, body) {
|
|
309
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-upgrade`, body);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Preview a package upgrade — the additive plan, informational removals,
|
|
313
|
+
* breaking contract changes, the binding drift report, and the modified-core
|
|
314
|
+
* report — with no writes. Admin-only.
|
|
315
|
+
*/
|
|
316
|
+
async previewAppPackageUpgrade(app_id, opts = {}) {
|
|
317
|
+
const query = opts.version !== undefined ? `?version=${opts.version}` : "";
|
|
318
|
+
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-upgrade${query}`);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Re-bind a package role to a different workspace group (the current group
|
|
322
|
+
* still exists). Re-materializes at the pinned version; refuses over
|
|
323
|
+
* modified-core findings. Admin-only.
|
|
324
|
+
*/
|
|
325
|
+
async rebindAppPackageRole(app_id, body) {
|
|
326
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-rebind-role`, body);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Workspace-wide dangling-reference sweep — active app/workflow artifacts
|
|
330
|
+
* whose prefixed schema ids no longer resolve. Backs
|
|
331
|
+
* `lotics workspace doctor`. Admin-only.
|
|
332
|
+
*/
|
|
333
|
+
async getWorkspaceDanglingReferences() {
|
|
334
|
+
return this.request("GET", "/v1/workspaces/dangling-references");
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Health check for a package installation — version pin vs. registry latest,
|
|
338
|
+
* binding drift, and locally modified core artifacts. Read-only; backs
|
|
339
|
+
* `lotics package doctor`. Admin-only.
|
|
340
|
+
*/
|
|
341
|
+
async getAppPackageHealth(app_id) {
|
|
342
|
+
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-health`);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Reset a package installation in a DEV workspace — drop the package-owned
|
|
346
|
+
* scaffolded tables and re-scaffold clean. Hard-gated server-side to dev
|
|
347
|
+
* workspaces (a non-dev workspace is refused). Returns the resulting app.
|
|
348
|
+
* Admin-only.
|
|
349
|
+
*/
|
|
350
|
+
async resetAppPackage(app_id) {
|
|
351
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-reset`);
|
|
352
|
+
}
|
|
193
353
|
/**
|
|
194
354
|
* Resolve the display name + fields (incl. select options) of the given tables
|
|
195
355
|
* — the schema `lotics app codegen` turns into the runtime `.lotics/app_fields.ts`
|
|
@@ -273,6 +433,10 @@ export class LoticsClient {
|
|
|
273
433
|
const qs = group_id ? `?group_id=${encodeURIComponent(group_id)}` : "";
|
|
274
434
|
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/members${qs}`);
|
|
275
435
|
}
|
|
436
|
+
/** A package installation's alias→id maps (fields/options/roles) — the app's runtime F/OPT resolution. */
|
|
437
|
+
async appBinding(app_id) {
|
|
438
|
+
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/binding`);
|
|
439
|
+
}
|
|
276
440
|
/**
|
|
277
441
|
* Resolve the full option set (key, label, color) of a named query's select
|
|
278
442
|
* columns — the picker companion to `appQuery`. Mirrors
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* { message }. Same shape as the production iframe-host's error path.
|
|
7
7
|
*/
|
|
8
8
|
import { LoticsClient } from "../client.js";
|
|
9
|
-
export type RpcOp = "query" | "field_options" | "workflow" | "members" | "context" | "upload_url" | "upload_complete" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts";
|
|
9
|
+
export type RpcOp = "query" | "field_options" | "workflow" | "members" | "context" | "binding" | "upload_url" | "upload_complete" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts";
|
|
10
10
|
export interface RpcRequest {
|
|
11
11
|
app_id: string;
|
|
12
12
|
op: RpcOp;
|
package/dist/dev/rpc_handler.js
CHANGED
|
@@ -11,6 +11,7 @@ const SUPPORTED_OPS = new Set([
|
|
|
11
11
|
"workflow",
|
|
12
12
|
"members",
|
|
13
13
|
"context",
|
|
14
|
+
"binding",
|
|
14
15
|
"upload_url",
|
|
15
16
|
"upload_complete",
|
|
16
17
|
"comments.list",
|
|
@@ -31,11 +32,16 @@ export async function dispatchRpc(client, body, opts) {
|
|
|
31
32
|
// In production the iframe host supplies the context; here we resolve the
|
|
32
33
|
// effective viewer from the CLI key (the view-as target when the client
|
|
33
34
|
// carries `x-view-as-member-id`, else the key's owner), so `useViewer`
|
|
34
|
-
// and `is_current_member` agree in the dev loop.
|
|
35
|
-
|
|
35
|
+
// and `is_current_member` agree in the dev loop. The installation's
|
|
36
|
+
// stored config is fetched live for the same parity: `useConfig()` must
|
|
37
|
+
// render the same values in the dev loop as in production, including
|
|
38
|
+
// edits made in the product mid-session (fresh on every app load, no
|
|
39
|
+
// dev-server restart).
|
|
40
|
+
const [who, app] = await Promise.all([client.whoami(), client.getApp(body.app_id)]);
|
|
36
41
|
return {
|
|
37
42
|
member_id: who.member_id,
|
|
38
43
|
comments_enabled: opts?.commentsEnabled ?? false,
|
|
44
|
+
config: app.config ?? {},
|
|
39
45
|
};
|
|
40
46
|
}
|
|
41
47
|
case "query": {
|
|
@@ -71,6 +77,8 @@ export async function dispatchRpc(client, body, opts) {
|
|
|
71
77
|
const p = body.payload;
|
|
72
78
|
return client.appMembers(body.app_id, p?.group);
|
|
73
79
|
}
|
|
80
|
+
case "binding":
|
|
81
|
+
return client.appBinding(body.app_id);
|
|
74
82
|
case "upload_url": {
|
|
75
83
|
const p = body.payload;
|
|
76
84
|
if (!p ||
|
|
@@ -12,15 +12,33 @@ const WHOAMI = {
|
|
|
12
12
|
organization_name: "Org",
|
|
13
13
|
};
|
|
14
14
|
describe("dispatchRpc — context op", () => {
|
|
15
|
-
it("resolves the viewer
|
|
16
|
-
const client = mockClient({
|
|
15
|
+
it("resolves the viewer from whoami and the installation's stored config from the live app row", async () => {
|
|
16
|
+
const client = mockClient({
|
|
17
|
+
whoami: async () => WHOAMI,
|
|
18
|
+
getApp: async () => ({
|
|
19
|
+
id: "app_x",
|
|
20
|
+
name: "App X",
|
|
21
|
+
workspace_id: "wsp_1",
|
|
22
|
+
current_version_id: null,
|
|
23
|
+
config: { page_size: 50 },
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
17
26
|
const result = await dispatchRpc(client, { app_id: "app_x", op: "context", payload: {} }, { commentsEnabled: true });
|
|
18
|
-
expect(result).toEqual({ member_id: "mbr_viewer", comments_enabled: true });
|
|
27
|
+
expect(result).toEqual({ member_id: "mbr_viewer", comments_enabled: true, config: { page_size: 50 } });
|
|
19
28
|
});
|
|
20
|
-
it("defaults comments_enabled to false
|
|
21
|
-
const client = mockClient({
|
|
29
|
+
it("defaults comments_enabled to false and config to empty for a bespoke app", async () => {
|
|
30
|
+
const client = mockClient({
|
|
31
|
+
whoami: async () => WHOAMI,
|
|
32
|
+
getApp: async () => ({
|
|
33
|
+
id: "app_x",
|
|
34
|
+
name: "App X",
|
|
35
|
+
workspace_id: "wsp_1",
|
|
36
|
+
current_version_id: null,
|
|
37
|
+
config: null,
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
22
40
|
const result = await dispatchRpc(client, { app_id: "app_x", op: "context", payload: {} });
|
|
23
|
-
expect(result).toEqual({ member_id: "mbr_viewer", comments_enabled: false });
|
|
41
|
+
expect(result).toEqual({ member_id: "mbr_viewer", comments_enabled: false, config: {} });
|
|
24
42
|
});
|
|
25
43
|
it("rejects an op that isn't supported", async () => {
|
|
26
44
|
const client = mockClient({});
|
|
@@ -46,6 +46,8 @@ export interface TableSchema {
|
|
|
46
46
|
* `upper` uppercases the result (TABLE aliases read as constants).
|
|
47
47
|
*/
|
|
48
48
|
export declare function slugifyAlias(name: string, upper: boolean): string;
|
|
49
|
+
/** A property key for an object literal — bare when a valid identifier, else quoted. */
|
|
50
|
+
export declare function propKey(alias: string): string;
|
|
49
51
|
/**
|
|
50
52
|
* Generate the full `.lotics/app_fields.ts` source. `tables` is the resolved
|
|
51
53
|
* workspace schema (the subset the app touches). An empty list yields valid,
|
|
@@ -68,7 +68,7 @@ function dedupeAliases(names, upper) {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
/** A property key for an object literal — bare when a valid identifier, else quoted. */
|
|
71
|
-
function propKey(alias) {
|
|
71
|
+
export function propKey(alias) {
|
|
72
72
|
return isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
73
73
|
}
|
|
74
74
|
/** Resolve table + field aliases once, so the `F`/`OPT` maps and the union types agree. */
|