@undefineds.co/xpod 0.3.14 → 0.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/config/local.json +5 -5
  2. package/config/xpod.json +24 -10
  3. package/dist/cli/commands/auth.d.ts +1 -0
  4. package/dist/cli/commands/auth.js +117 -37
  5. package/dist/cli/commands/auth.js.map +1 -1
  6. package/dist/cli/commands/login.js +16 -23
  7. package/dist/cli/commands/login.js.map +1 -1
  8. package/dist/cli/commands/logs.d.ts +2 -0
  9. package/dist/cli/commands/logs.js +20 -5
  10. package/dist/cli/commands/logs.js.map +1 -1
  11. package/dist/cli/commands/obj.d.ts +44 -0
  12. package/dist/cli/commands/obj.js +1059 -0
  13. package/dist/cli/commands/obj.js.map +1 -0
  14. package/dist/cli/commands/rdf.d.ts +14 -0
  15. package/dist/cli/commands/rdf.js +235 -0
  16. package/dist/cli/commands/rdf.js.map +1 -0
  17. package/dist/cli/commands/resource.d.ts +31 -0
  18. package/dist/cli/commands/resource.js +191 -0
  19. package/dist/cli/commands/resource.js.map +1 -0
  20. package/dist/cli/commands/secret.d.ts +36 -0
  21. package/dist/cli/commands/secret.js +285 -0
  22. package/dist/cli/commands/secret.js.map +1 -0
  23. package/dist/cli/commands/server.d.ts +11 -0
  24. package/dist/cli/commands/server.js +168 -0
  25. package/dist/cli/commands/server.js.map +1 -0
  26. package/dist/cli/commands/start.d.ts +1 -0
  27. package/dist/cli/commands/start.js +5 -0
  28. package/dist/cli/commands/start.js.map +1 -1
  29. package/dist/cli/commands/status.d.ts +1 -0
  30. package/dist/cli/commands/status.js +21 -6
  31. package/dist/cli/commands/status.js.map +1 -1
  32. package/dist/cli/commands/stop.d.ts +3 -0
  33. package/dist/cli/commands/stop.js +40 -6
  34. package/dist/cli/commands/stop.js.map +1 -1
  35. package/dist/cli/index.js +23 -8
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/lib/auth-context.d.ts +24 -0
  38. package/dist/cli/lib/auth-context.js +70 -0
  39. package/dist/cli/lib/auth-context.js.map +1 -0
  40. package/dist/cli/lib/output.d.ts +23 -0
  41. package/dist/cli/lib/output.js +63 -0
  42. package/dist/cli/lib/output.js.map +1 -0
  43. package/dist/cli/lib/resource.d.ts +29 -0
  44. package/dist/cli/lib/resource.js +114 -0
  45. package/dist/cli/lib/resource.js.map +1 -0
  46. package/dist/components/context.jsonld +6 -0
  47. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.d.ts +11 -10
  48. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +13 -24
  49. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
  50. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld +4 -4
  51. package/dist/identity/oidc/AutoDetectOidcHandler.d.ts +8 -4
  52. package/dist/identity/oidc/AutoDetectOidcHandler.js +10 -6
  53. package/dist/identity/oidc/AutoDetectOidcHandler.js.map +1 -1
  54. package/dist/identity/oidc/AutoDetectOidcHandler.jsonld +3 -3
  55. package/dist/storage/accessors/MixDataAccessor.js +3 -0
  56. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  57. package/dist/storage/quint/SqliteQuintStore.d.ts +26 -1
  58. package/dist/storage/quint/SqliteQuintStore.js +551 -318
  59. package/dist/storage/quint/SqliteQuintStore.js.map +1 -1
  60. package/dist/storage/quint/SqliteQuintStore.jsonld +102 -2
  61. package/dist/storage/quint/schema.d.ts +76 -0
  62. package/dist/storage/quint/schema.js +13 -7
  63. package/dist/storage/quint/schema.js.map +1 -1
  64. package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +4 -1
  65. package/dist/storage/rdf/RdfLocalQueryEngine.js +77 -8
  66. package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -1
  67. package/dist/storage/rdf/SolidRdfEngine.d.ts +5 -0
  68. package/dist/storage/rdf/SolidRdfEngine.js +31 -3
  69. package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
  70. package/dist/storage/rdf/SolidRdfEngine.jsonld +34 -0
  71. package/dist/storage/sparql/ComunicaQuintEngine.js +16 -3
  72. package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
  73. package/package.json +1 -1
  74. package/dist/cli/commands/config.d.ts +0 -42
  75. package/dist/cli/commands/config.js +0 -289
  76. package/dist/cli/commands/config.js.map +0 -1
@@ -0,0 +1,1059 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.objCommand = void 0;
4
+ exports.redactDescriptorObject = redactDescriptorObject;
5
+ exports.buildDescriptorUpsertSparql = buildDescriptorUpsertSparql;
6
+ exports.buildDescriptorPatchSparql = buildDescriptorPatchSparql;
7
+ exports.buildDescriptorLinkSparql = buildDescriptorLinkSparql;
8
+ exports.buildDescriptorDeleteSparql = buildDescriptorDeleteSparql;
9
+ exports.extractReservedWhereSelectors = extractReservedWhereSelectors;
10
+ exports.buildDescriptorObjectQuery = buildDescriptorObjectQuery;
11
+ const fs_1 = require("fs");
12
+ const models_1 = require("@undefineds.co/models");
13
+ const auth_context_1 = require("../lib/auth-context");
14
+ const output_1 = require("../lib/output");
15
+ const resource_1 = require("../lib/resource");
16
+ const rdf_1 = require("./rdf");
17
+ function objOptions(yargs) {
18
+ return yargs
19
+ .option('url', {
20
+ alias: 'u',
21
+ type: 'string',
22
+ description: 'Server base URL override',
23
+ })
24
+ .option('json', {
25
+ type: 'boolean',
26
+ default: false,
27
+ description: 'Output JSON envelope',
28
+ });
29
+ }
30
+ function selectorOptions(yargs) {
31
+ return objOptions(yargs)
32
+ .option('schema', { type: 'string', description: 'Schema URI or descriptor alias' })
33
+ .option('subject', { type: 'string', description: 'Exact object subject URI or Pod-relative subject' })
34
+ .option('resource', { type: 'string', description: 'Exact RDF document/resource URI or Pod-relative path' })
35
+ .option('path', { type: 'string', description: 'Pod-relative resource path' })
36
+ .option('where', { type: 'string', description: 'JSON object of descriptor field filters' })
37
+ .option('status', { type: 'string', description: 'Shortcut for --where {"status": "..."} when the descriptor has a status field' })
38
+ .option('relation', { type: 'array', string: true, description: 'Descriptor URI relation filter, field=uri. Repeatable.' })
39
+ .option('limit', { type: 'number', default: 100, description: 'Maximum number of rows' })
40
+ .option('include-metadata', { type: 'boolean', default: true, description: 'Include etag/revision metadata when available' });
41
+ }
42
+ function mutationModeCheck(argv) {
43
+ if (argv['dry-run'] === argv.commit) {
44
+ throw new Error('Specify exactly one of --dry-run or --commit.');
45
+ }
46
+ return true;
47
+ }
48
+ function isRecord(value) {
49
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
50
+ }
51
+ function parseJsonObject(value, code) {
52
+ try {
53
+ const parsed = JSON.parse(value);
54
+ if (!isRecord(parsed)) {
55
+ throw new Error('Expected a JSON object.');
56
+ }
57
+ return parsed;
58
+ }
59
+ catch (error) {
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ throw new output_1.CliCommandError(code, message, 2);
62
+ }
63
+ }
64
+ async function readStdinText() {
65
+ const chunks = [];
66
+ for await (const chunk of process.stdin) {
67
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
68
+ }
69
+ return Buffer.concat(chunks).toString('utf-8');
70
+ }
71
+ async function readTextInput(input) {
72
+ return input === '-' ? readStdinText() : (0, fs_1.readFileSync)(input, 'utf-8');
73
+ }
74
+ async function readJsonl(input) {
75
+ return (await readTextInput(input))
76
+ .split(/\r?\n/u)
77
+ .map((line) => line.trim())
78
+ .filter(Boolean)
79
+ .map((line, index) => {
80
+ try {
81
+ const parsed = JSON.parse(line);
82
+ if (!isRecord(parsed)) {
83
+ throw new Error('Expected a JSON object.');
84
+ }
85
+ return parsed;
86
+ }
87
+ catch (error) {
88
+ const message = error instanceof Error ? error.message : String(error);
89
+ throw new output_1.CliCommandError('invalid_jsonl', `Invalid JSONL at line ${index + 1}: ${message}`, 2);
90
+ }
91
+ });
92
+ }
93
+ function descriptorLocalName(value) {
94
+ return value.split(/[\/#]/u).filter(Boolean).pop() ?? value;
95
+ }
96
+ function resolveDescriptorOrNull(schema) {
97
+ const exact = models_1.podSchema.describe({ schemaUri: schema });
98
+ if (exact)
99
+ return exact;
100
+ const normalized = schema.trim().toLowerCase();
101
+ return models_1.podSchema.list().find((descriptor) => descriptor.resourceKind.toLowerCase() === normalized ||
102
+ descriptorLocalName(descriptor.uri).toLowerCase() === normalized ||
103
+ descriptorLocalName(descriptor.class).toLowerCase() === normalized) ?? null;
104
+ }
105
+ function resolveDescriptor(schema) {
106
+ const descriptor = resolveDescriptorOrNull(schema);
107
+ if (!descriptor) {
108
+ throw new output_1.CliCommandError('schema_unknown', `Schema is not known by @undefineds.co/models: ${schema}`, 2);
109
+ }
110
+ return descriptor;
111
+ }
112
+ function resourceUrlFromPlan(podRoot, plan) {
113
+ return new URL(plan.resourceUri.replace(/^\/+/, ''), podRoot).toString();
114
+ }
115
+ function varName(field) {
116
+ return `v_${field.replace(/[^A-Za-z0-9_]/gu, '_')}`;
117
+ }
118
+ function fieldVar(field) {
119
+ return `?${varName(field)}`;
120
+ }
121
+ function sparqlIri(value) {
122
+ if (!/^https?:\/\//iu.test(value)) {
123
+ throw new output_1.CliCommandError('invalid_uri', `Expected an absolute URI: ${value}`, 2);
124
+ }
125
+ return `<${value.replace(/[<>]/gu, '')}>`;
126
+ }
127
+ function sparqlValue(value, field) {
128
+ if (field?.type === 'uri') {
129
+ if (typeof value !== 'string') {
130
+ throw new output_1.CliCommandError('invalid_uri', `Field expects a URI value: ${String(value)}`, 2);
131
+ }
132
+ return sparqlIri(value);
133
+ }
134
+ if (typeof value === 'number' || typeof value === 'boolean') {
135
+ return JSON.stringify(value);
136
+ }
137
+ if (field?.type === 'json') {
138
+ return JSON.stringify(JSON.stringify(value));
139
+ }
140
+ return JSON.stringify(String(value));
141
+ }
142
+ function assertKnownFields(descriptor, values, code = 'field_unknown') {
143
+ const unknown = Object.keys(values).filter((field) => !descriptor.fields[field]);
144
+ if (unknown.length > 0) {
145
+ throw new output_1.CliCommandError(code, `Fields are not known by descriptor ${descriptor.uri}: ${unknown.join(', ')}`, 2);
146
+ }
147
+ }
148
+ function assertWritableFields(descriptor, values) {
149
+ assertKnownFields(descriptor, values);
150
+ const invalid = Object.keys(values).filter((field) => !descriptor.writableFields.includes(field));
151
+ if (invalid.length > 0) {
152
+ throw new output_1.CliCommandError('invalid_set_fields', `Fields are not writable: ${invalid.join(', ')}`, 2);
153
+ }
154
+ }
155
+ function redactDescriptorObject(descriptor, value) {
156
+ const redacted = {};
157
+ for (const [key, item] of Object.entries(value)) {
158
+ redacted[key] = descriptor.fields[key]?.secret ? '[redacted]' : item;
159
+ }
160
+ return redacted;
161
+ }
162
+ function buildDescriptorUpsertSparql(descriptor, subject, row) {
163
+ const fields = descriptor.fields;
164
+ const match = row.match ?? {};
165
+ const set = row.set ?? {};
166
+ assertKnownFields(descriptor, match);
167
+ assertWritableFields(descriptor, set);
168
+ const merged = { ...match, ...set };
169
+ const deleteFields = Object.keys(set).filter((field) => fields[field]);
170
+ const deletes = deleteFields.map((field) => `<${subject}> <${fields[field].predicate}> ?old_${field.replace(/[^A-Za-z0-9_]/gu, '_')} .`);
171
+ const optionals = deleteFields.map((field) => `OPTIONAL { <${subject}> <${fields[field].predicate}> ?old_${field.replace(/[^A-Za-z0-9_]/gu, '_')} }`);
172
+ const inserts = [
173
+ `<${subject}> a <${descriptor.class}>`,
174
+ ...Object.entries(merged)
175
+ .filter(([field, value]) => fields[field] && value !== undefined && value !== null)
176
+ .map(([field, value]) => `<${subject}> <${fields[field].predicate}> ${sparqlValue(value, fields[field])}`),
177
+ ];
178
+ return `DELETE {\n ${deletes.join('\n ')}\n}\nINSERT {\n ${inserts.join(' .\n ')} .\n}\nWHERE {\n ${optionals.join('\n ')}\n}`;
179
+ }
180
+ function buildDescriptorPatchSparql(descriptor, subject, set) {
181
+ return buildDescriptorUpsertSparql(descriptor, subject, { set });
182
+ }
183
+ function buildDescriptorLinkSparql(subject, predicate, object) {
184
+ return `INSERT DATA {\n <${subject}> <${predicate}> ${sparqlIri(object)} .\n}`;
185
+ }
186
+ function buildDescriptorDeleteSparql(subject) {
187
+ return `DELETE {\n <${subject}> ?p ?o .\n}\nWHERE {\n <${subject}> ?p ?o .\n}`;
188
+ }
189
+ function parseWhere(where) {
190
+ return where ? parseJsonObject(where, 'invalid_where') : {};
191
+ }
192
+ function extractReservedWhereSelectors(where) {
193
+ const filters = { ...where };
194
+ const selectors = {};
195
+ for (const key of ['subject', 'resource', 'path']) {
196
+ const value = filters[key];
197
+ if (value === undefined)
198
+ continue;
199
+ if (typeof value !== 'string') {
200
+ throw new output_1.CliCommandError('invalid_where', `Reserved selector field "${key}" must be a string.`, 2);
201
+ }
202
+ selectors[key] = value;
203
+ delete filters[key];
204
+ }
205
+ return { where: filters, ...selectors };
206
+ }
207
+ function applyReservedSelector(current, reserved, key) {
208
+ if (!reserved)
209
+ return current;
210
+ if (current && current !== reserved) {
211
+ throw new output_1.CliCommandError('selector_conflict', `Conflicting ${key} selectors were provided.`, 2);
212
+ }
213
+ return reserved;
214
+ }
215
+ function parseRelations(input) {
216
+ const relations = {};
217
+ for (const item of input ?? []) {
218
+ const trimmed = item.trim();
219
+ if (!trimmed)
220
+ continue;
221
+ if (trimmed.startsWith('{')) {
222
+ const parsed = parseJsonObject(trimmed, 'invalid_relation');
223
+ for (const [key, value] of Object.entries(parsed)) {
224
+ if (typeof value !== 'string') {
225
+ throw new output_1.CliCommandError('invalid_relation', `Relation filter ${key} must be a URI string.`, 2);
226
+ }
227
+ relations[key] = value;
228
+ }
229
+ continue;
230
+ }
231
+ const eq = trimmed.indexOf('=');
232
+ if (eq <= 0) {
233
+ throw new output_1.CliCommandError('invalid_relation', `Relation filter must be field=uri: ${trimmed}`, 2);
234
+ }
235
+ relations[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
236
+ }
237
+ return relations;
238
+ }
239
+ function classifySelector(selector) {
240
+ if (resolveDescriptorOrNull(selector))
241
+ return 'schema';
242
+ if (selector.includes('#'))
243
+ return 'subject';
244
+ if (/^https?:\/\//iu.test(selector))
245
+ return 'resource';
246
+ if (selector.includes('/'))
247
+ return 'path';
248
+ return 'schema';
249
+ }
250
+ async function inferDescriptorForSubject(context, subject) {
251
+ const target = (0, resource_1.resolveResourceTarget)(context, (0, rdf_1.documentResourceInput)(subject));
252
+ const response = await (0, resource_1.fetchResource)(context, target, {
253
+ method: 'GET',
254
+ headers: { Accept: 'text/turtle' },
255
+ });
256
+ (0, resource_1.ensureOk)(response, response.status === 404 ? 'resource_not_found' : 'schema_infer_failed', `Failed to infer schema from ${subject}`);
257
+ const body = await response.text();
258
+ const matches = models_1.podSchema.list().filter((descriptor) => body.includes(`<${descriptor.class}>`));
259
+ if (matches.length === 1) {
260
+ return matches[0];
261
+ }
262
+ if (matches.length > 1) {
263
+ throw new output_1.CliCommandError('schema_ambiguous', 'Multiple known descriptor classes were found. Re-run with --schema.', 2, {
264
+ schemas: matches.map((descriptor) => descriptor.uri),
265
+ });
266
+ }
267
+ throw new output_1.CliCommandError('schema_required', 'Could not infer a known schema from the subject document. Re-run with --schema.', 2);
268
+ }
269
+ async function resolveObjectSelector(context, argv) {
270
+ let schema = argv.schema;
271
+ let subject = argv.subject;
272
+ let resource = argv.resource;
273
+ let path = argv.path;
274
+ if (argv.selector) {
275
+ const kind = classifySelector(argv.selector);
276
+ if (kind === 'schema' && !schema)
277
+ schema = argv.selector;
278
+ if (kind === 'subject' && !subject)
279
+ subject = argv.selector;
280
+ if (kind === 'resource' && !resource)
281
+ resource = argv.selector;
282
+ if (kind === 'path' && !path)
283
+ path = argv.selector;
284
+ }
285
+ const parsedWhere = parseWhere(argv.where);
286
+ if (argv.status !== undefined) {
287
+ parsedWhere.status = argv.status;
288
+ }
289
+ const { where, subject: whereSubject, resource: whereResource, path: wherePath, } = extractReservedWhereSelectors(parsedWhere);
290
+ subject = applyReservedSelector(subject, whereSubject, 'subject');
291
+ resource = applyReservedSelector(resource, whereResource, 'resource');
292
+ path = applyReservedSelector(path, wherePath, 'path');
293
+ const subjectUrl = subject ? (0, resource_1.resolveResourceTarget)(context, subject).resourceUrl : undefined;
294
+ const resourceUrl = resource
295
+ ? (0, resource_1.resolveResourceTarget)(context, (0, rdf_1.documentResourceInput)(resource)).resourceUrl
296
+ : path
297
+ ? (0, resource_1.resolveResourceTarget)(context, (0, rdf_1.documentResourceInput)(path)).resourceUrl
298
+ : subjectUrl
299
+ ? (0, rdf_1.documentResourceInput)(subjectUrl)
300
+ : undefined;
301
+ const descriptor = schema
302
+ ? resolveDescriptor(schema)
303
+ : subjectUrl
304
+ ? await inferDescriptorForSubject(context, subjectUrl)
305
+ : undefined;
306
+ if (!descriptor) {
307
+ throw new output_1.CliCommandError('schema_required', 'Object selectors require --schema, a schema selector, or an exact subject with an inferable RDF type.', 2);
308
+ }
309
+ return {
310
+ descriptor,
311
+ subject: subjectUrl,
312
+ resourceUrl,
313
+ where,
314
+ relations: parseRelations(argv.relation),
315
+ limit: Math.max(1, Math.min(argv.limit || 100, 10000)),
316
+ includeMetadata: argv['include-metadata'] !== false,
317
+ };
318
+ }
319
+ function queryFilterTerm(field, value) {
320
+ if (Array.isArray(value)) {
321
+ return value.map((item) => sparqlValue(item, field)).join(' ');
322
+ }
323
+ return sparqlValue(value, field);
324
+ }
325
+ function buildDescriptorObjectQuery(selector) {
326
+ const descriptor = selector.descriptor;
327
+ assertKnownFields(descriptor, selector.where);
328
+ const triples = [`?subject a <${descriptor.class}> .`];
329
+ if (selector.subject) {
330
+ triples.push(`VALUES ?subject { <${selector.subject}> }`);
331
+ }
332
+ if (selector.resourceUrl) {
333
+ triples.push(`FILTER(STR(?subject) = ${JSON.stringify(selector.resourceUrl)} || STRSTARTS(STR(?subject), ${JSON.stringify(`${selector.resourceUrl}#`)}))`);
334
+ }
335
+ for (const [field, fieldDescriptor] of Object.entries(descriptor.fields)) {
336
+ triples.push(`OPTIONAL { ?subject <${fieldDescriptor.predicate}> ${fieldVar(field)} . }`);
337
+ }
338
+ for (const [field, value] of Object.entries(selector.where)) {
339
+ const descriptorField = descriptor.fields[field];
340
+ if (!descriptorField)
341
+ continue;
342
+ if (Array.isArray(value)) {
343
+ triples.push(`VALUES ${fieldVar(`filter_${field}`)} { ${queryFilterTerm(descriptorField, value)} }`);
344
+ triples.push(`?subject <${descriptorField.predicate}> ${fieldVar(`filter_${field}`)} .`);
345
+ }
346
+ else {
347
+ triples.push(`?subject <${descriptorField.predicate}> ${queryFilterTerm(descriptorField, value)} .`);
348
+ }
349
+ }
350
+ for (const [field, objectUri] of Object.entries(selector.relations)) {
351
+ const descriptorField = descriptor.fields[field];
352
+ if (!descriptorField) {
353
+ throw new output_1.CliCommandError('predicate_unknown', `Relation field is not known by descriptor ${descriptor.uri}: ${field}`, 2);
354
+ }
355
+ if (descriptorField.type !== 'uri') {
356
+ throw new output_1.CliCommandError('relation_field_not_uri', `Relation field is not URI-valued: ${field}`, 2);
357
+ }
358
+ triples.push(`?subject <${descriptorField.predicate}> ${sparqlIri(objectUri)} .`);
359
+ }
360
+ const fieldVars = Object.keys(descriptor.fields).map(fieldVar).join(' ');
361
+ return `SELECT ?subject ${fieldVars} WHERE {\n ${triples.join('\n ')}\n}\nLIMIT ${selector.limit}`;
362
+ }
363
+ function bindingToValue(binding, field) {
364
+ if (!binding?.value)
365
+ return undefined;
366
+ if (binding.type === 'uri')
367
+ return binding.value;
368
+ if (field.type === 'number') {
369
+ const number = Number(binding.value);
370
+ return Number.isNaN(number) ? binding.value : number;
371
+ }
372
+ if (field.type === 'boolean') {
373
+ return binding.value === 'true' || binding.value === '1';
374
+ }
375
+ if (field.type === 'json') {
376
+ try {
377
+ return JSON.parse(binding.value);
378
+ }
379
+ catch {
380
+ return binding.value;
381
+ }
382
+ }
383
+ return binding.value;
384
+ }
385
+ async function applyEtags(context, objects) {
386
+ const cache = new Map();
387
+ for (const object of objects) {
388
+ if (!cache.has(object.resourceUrl)) {
389
+ try {
390
+ const target = (0, resource_1.resolveResourceTarget)(context, object.resourceUrl);
391
+ const response = await (0, resource_1.fetchResource)(context, target, { method: 'HEAD' });
392
+ cache.set(object.resourceUrl, response.ok ? response.headers.get('etag') : null);
393
+ }
394
+ catch {
395
+ cache.set(object.resourceUrl, null);
396
+ }
397
+ }
398
+ const etag = cache.get(object.resourceUrl) ?? null;
399
+ object.etag = etag;
400
+ object.revision = etag;
401
+ }
402
+ }
403
+ async function queryDescriptorObjects(context, selector) {
404
+ const endpoint = (0, rdf_1.resolveSparqlEndpoint)(context.podRoot);
405
+ const query = buildDescriptorObjectQuery(selector);
406
+ const response = await (0, resource_1.fetchResource)(context, {
407
+ input: endpoint,
408
+ resourceUrl: endpoint,
409
+ webId: context.webId,
410
+ podRoot: context.podRoot,
411
+ baseIri: context.baseIri,
412
+ }, {
413
+ method: 'POST',
414
+ headers: {
415
+ Accept: 'application/sparql-results+json',
416
+ 'Content-Type': 'application/sparql-query',
417
+ },
418
+ body: query,
419
+ });
420
+ (0, resource_1.ensureOk)(response, 'obj_query_failed', `Failed to list objects for ${selector.descriptor.uri}`);
421
+ const result = (await response.json());
422
+ const objects = (result.results?.bindings ?? []).flatMap((binding) => {
423
+ const subject = binding.subject?.value;
424
+ if (!subject)
425
+ return [];
426
+ const object = {};
427
+ for (const [field, descriptorField] of Object.entries(selector.descriptor.fields)) {
428
+ const value = bindingToValue(binding[varName(field)], descriptorField);
429
+ if (value !== undefined) {
430
+ object[field] = descriptorField.secret ? '[redacted]' : value;
431
+ }
432
+ }
433
+ return [{
434
+ schema: selector.descriptor.uri,
435
+ subject,
436
+ resourceUrl: (0, rdf_1.documentResourceInput)(subject),
437
+ object,
438
+ }];
439
+ });
440
+ if (selector.includeMetadata) {
441
+ await applyEtags(context, objects);
442
+ }
443
+ return objects;
444
+ }
445
+ function jsonlForObjects(objects) {
446
+ return `${objects.map((object, index) => JSON.stringify({
447
+ index,
448
+ ok: true,
449
+ code: 'ok',
450
+ ...object,
451
+ })).join('\n')}${objects.length > 0 ? '\n' : ''}`;
452
+ }
453
+ async function patchSubject(input) {
454
+ const target = (0, resource_1.resolveResourceTarget)(input.context, (0, rdf_1.documentResourceInput)(input.subject));
455
+ const headers = { 'Content-Type': 'application/sparql-update' };
456
+ if (input.ifMatch)
457
+ headers['If-Match'] = input.ifMatch;
458
+ const response = await (0, resource_1.fetchResource)(input.context, target, {
459
+ method: 'PATCH',
460
+ headers,
461
+ body: input.sparql,
462
+ });
463
+ (0, resource_1.ensureOk)(response, input.errorCode, `Failed to patch object ${input.subject}`);
464
+ return (0, resource_1.responseData)(target, response);
465
+ }
466
+ function mutationPlan(input) {
467
+ return {
468
+ operationId: input.operationId,
469
+ webId: input.context.webId,
470
+ podRoot: input.context.podRoot,
471
+ summary: input.summary,
472
+ risk: 'normal',
473
+ resources: [
474
+ {
475
+ subject: input.subject,
476
+ schema: input.schema,
477
+ etag: input.etag,
478
+ change: input.change,
479
+ },
480
+ ],
481
+ diff: input.diff ?? [],
482
+ };
483
+ }
484
+ async function resolveDescriptorForMutation(context, schema, subject) {
485
+ return schema ? resolveDescriptor(schema) : inferDescriptorForSubject(context, subject);
486
+ }
487
+ async function executePatchCommand(argv) {
488
+ const context = await (0, auth_context_1.requireAuthContext)(argv);
489
+ const subject = (0, resource_1.resolveResourceTarget)(context, argv.subject).resourceUrl;
490
+ const descriptor = await resolveDescriptorForMutation(context, argv.schema, subject);
491
+ const set = parseJsonObject(argv.set, 'invalid_set');
492
+ const sparql = buildDescriptorPatchSparql(descriptor, subject, set);
493
+ const plan = mutationPlan({
494
+ operationId: `op_patch_${Date.now()}`,
495
+ context,
496
+ summary: `Patch one descriptor-backed ${descriptor.resourceKind}`,
497
+ subject,
498
+ schema: descriptor.uri,
499
+ etag: argv['if-match'],
500
+ change: 'patch',
501
+ diff: [redactDescriptorObject(descriptor, set)],
502
+ });
503
+ if (argv['dry-run']) {
504
+ return { plan };
505
+ }
506
+ const response = await patchSubject({
507
+ context,
508
+ subject,
509
+ sparql,
510
+ ifMatch: argv['if-match'],
511
+ errorCode: 'obj_patch_failed',
512
+ });
513
+ return { ...response, plan };
514
+ }
515
+ async function executeLinkCommand(argv) {
516
+ const context = await (0, auth_context_1.requireAuthContext)(argv);
517
+ const subject = (0, resource_1.resolveResourceTarget)(context, argv.subject).resourceUrl;
518
+ const descriptor = argv.schema || !/^https?:\/\//iu.test(argv.predicate)
519
+ ? await resolveDescriptorForMutation(context, argv.schema, subject)
520
+ : undefined;
521
+ let predicate = argv.predicate;
522
+ if (!/^https?:\/\//iu.test(predicate)) {
523
+ if (!descriptor?.fields[predicate]) {
524
+ throw new output_1.CliCommandError('predicate_unknown', `Predicate field is not known by descriptor: ${predicate}`, 2);
525
+ }
526
+ const field = descriptor.fields[predicate];
527
+ if (field.type !== 'uri') {
528
+ throw new output_1.CliCommandError('relation_field_not_uri', `Relation field is not URI-valued: ${predicate}`, 2);
529
+ }
530
+ predicate = field.predicate;
531
+ }
532
+ const sparql = buildDescriptorLinkSparql(subject, predicate, argv.object);
533
+ const plan = mutationPlan({
534
+ operationId: `op_link_${Date.now()}`,
535
+ context,
536
+ summary: 'Link one descriptor-backed object relation',
537
+ subject,
538
+ schema: descriptor?.uri,
539
+ etag: argv['if-match'],
540
+ change: 'link',
541
+ diff: [{ predicate, object: argv.object }],
542
+ });
543
+ if (argv['dry-run']) {
544
+ return { plan };
545
+ }
546
+ const response = await patchSubject({
547
+ context,
548
+ subject,
549
+ sparql,
550
+ ifMatch: argv['if-match'],
551
+ errorCode: 'obj_link_failed',
552
+ });
553
+ return { ...response, plan };
554
+ }
555
+ async function executeDeleteCommand(argv) {
556
+ const context = await (0, auth_context_1.requireAuthContext)(argv);
557
+ const subject = (0, resource_1.resolveResourceTarget)(context, argv.subject).resourceUrl;
558
+ const descriptor = await resolveDescriptorForMutation(context, argv.schema, subject);
559
+ const sparql = buildDescriptorDeleteSparql(subject);
560
+ const plan = mutationPlan({
561
+ operationId: `op_delete_${Date.now()}`,
562
+ context,
563
+ summary: `Delete one descriptor-backed ${descriptor.resourceKind}`,
564
+ subject,
565
+ schema: descriptor.uri,
566
+ etag: argv['if-match'],
567
+ change: 'delete',
568
+ });
569
+ if (argv['dry-run']) {
570
+ return { plan };
571
+ }
572
+ const response = await patchSubject({
573
+ context,
574
+ subject,
575
+ sparql,
576
+ ifMatch: argv['if-match'],
577
+ errorCode: 'obj_delete_failed',
578
+ });
579
+ return { ...response, plan };
580
+ }
581
+ async function executeRawRow(context, row, index, commit) {
582
+ const op = row.op;
583
+ if (!op) {
584
+ throw new output_1.CliCommandError('schema_required', 'JSONL row without schema must declare an explicit raw resource op.', 2);
585
+ }
586
+ const targetInput = row.path ?? row.resource ?? row.subject;
587
+ if (!targetInput) {
588
+ throw new output_1.CliCommandError('explicit_target_required', 'Raw JSONL rows must declare path, resource, or subject.', 2);
589
+ }
590
+ const target = (0, resource_1.resolveResourceTarget)(context, row.subject ? (0, rdf_1.documentResourceInput)(targetInput) : targetInput);
591
+ if (!commit) {
592
+ return {
593
+ index,
594
+ ok: true,
595
+ code: 'plan_ready',
596
+ operation: op,
597
+ resourceUrl: target.resourceUrl,
598
+ webId: context.webId,
599
+ podRoot: context.podRoot,
600
+ };
601
+ }
602
+ if (!['put', 'patch', 'delete'].includes(op)) {
603
+ throw new output_1.CliCommandError('unsupported_operation', `Unsupported raw JSONL operation: ${op}`, 2);
604
+ }
605
+ const headers = {};
606
+ let body;
607
+ if (op !== 'delete') {
608
+ if (row.from) {
609
+ const file = (0, resource_1.readBodyFile)(row.from);
610
+ body = file.body;
611
+ headers['Content-Type'] = row.contentType ?? file.contentType;
612
+ }
613
+ else if (row.body !== undefined) {
614
+ body = row.body;
615
+ headers['Content-Type'] = row.contentType ?? 'text/plain';
616
+ }
617
+ else {
618
+ throw new output_1.CliCommandError('body_required', `Raw ${op} rows require body or from.`, 2);
619
+ }
620
+ }
621
+ if (row.ifMatch)
622
+ headers['If-Match'] = row.ifMatch;
623
+ const response = await (0, resource_1.fetchResource)(context, target, {
624
+ method: op.toUpperCase(),
625
+ headers,
626
+ body,
627
+ });
628
+ (0, resource_1.ensureOk)(response, 'raw_commit_failed', `Failed to commit raw row ${index}`);
629
+ return {
630
+ index,
631
+ ok: true,
632
+ code: 'committed',
633
+ operation: op,
634
+ ...(0, resource_1.responseData)(target, response),
635
+ };
636
+ }
637
+ async function executeDescriptorRow(context, row, index, commit) {
638
+ const descriptor = resolveDescriptor(row.schema);
639
+ const op = row.op ?? 'upsert';
640
+ if (op !== 'upsert') {
641
+ if (!row.subject) {
642
+ throw new output_1.CliCommandError('subject_required', `Object operation ${op} requires subject.`, 2);
643
+ }
644
+ const subject = (0, resource_1.resolveResourceTarget)(context, row.subject).resourceUrl;
645
+ if (op === 'patch') {
646
+ const set = row.set ?? {};
647
+ const sparql = buildDescriptorPatchSparql(descriptor, subject, set);
648
+ if (commit) {
649
+ await patchSubject({ context, subject, sparql, ifMatch: row.ifMatch, errorCode: 'obj_patch_failed' });
650
+ }
651
+ return {
652
+ index,
653
+ ok: true,
654
+ code: commit ? 'committed' : 'plan_ready',
655
+ operation: op,
656
+ schema: descriptor.uri,
657
+ subject,
658
+ set: redactDescriptorObject(descriptor, set),
659
+ };
660
+ }
661
+ if (op === 'link') {
662
+ if (!row.predicate || !row.object) {
663
+ throw new output_1.CliCommandError('link_target_required', 'Link rows require predicate and object.', 2);
664
+ }
665
+ const field = descriptor.fields[row.predicate];
666
+ const predicate = field ? field.predicate : row.predicate;
667
+ if (field && field.type !== 'uri') {
668
+ throw new output_1.CliCommandError('relation_field_not_uri', `Relation field is not URI-valued: ${row.predicate}`, 2);
669
+ }
670
+ if (!field && !/^https?:\/\//iu.test(predicate)) {
671
+ throw new output_1.CliCommandError('predicate_unknown', `Predicate field is not known by descriptor: ${row.predicate}`, 2);
672
+ }
673
+ const sparql = buildDescriptorLinkSparql(subject, predicate, row.object);
674
+ if (commit) {
675
+ await patchSubject({ context, subject, sparql, ifMatch: row.ifMatch, errorCode: 'obj_link_failed' });
676
+ }
677
+ return {
678
+ index,
679
+ ok: true,
680
+ code: commit ? 'committed' : 'plan_ready',
681
+ operation: op,
682
+ schema: descriptor.uri,
683
+ subject,
684
+ predicate,
685
+ object: row.object,
686
+ };
687
+ }
688
+ if (op === 'delete') {
689
+ const sparql = buildDescriptorDeleteSparql(subject);
690
+ if (commit) {
691
+ await patchSubject({ context, subject, sparql, ifMatch: row.ifMatch, errorCode: 'obj_delete_failed' });
692
+ }
693
+ return {
694
+ index,
695
+ ok: true,
696
+ code: commit ? 'committed' : 'plan_ready',
697
+ operation: op,
698
+ schema: descriptor.uri,
699
+ subject,
700
+ };
701
+ }
702
+ throw new output_1.CliCommandError('unsupported_operation', `Unsupported object operation: ${op}`, 2);
703
+ }
704
+ const storage = (0, models_1.createPodStorage)();
705
+ const validation = storage.validate({
706
+ schemaUri: descriptor.uri,
707
+ operation: 'upsert',
708
+ match: row.match ?? {},
709
+ set: row.set ?? {},
710
+ });
711
+ if (!validation.ok) {
712
+ throw new output_1.CliCommandError(validation.error.code, validation.error.message, 2);
713
+ }
714
+ const subject = resourceUrlFromPlan(context.podRoot, validation.plan);
715
+ if (commit) {
716
+ const sparql = buildDescriptorUpsertSparql(descriptor, subject, row);
717
+ await patchSubject({ context, subject, sparql, ifMatch: row.ifMatch, errorCode: 'obj_commit_failed' });
718
+ }
719
+ return {
720
+ index,
721
+ ok: true,
722
+ code: commit ? 'committed' : 'plan_ready',
723
+ operation: op,
724
+ schema: descriptor.uri,
725
+ subject,
726
+ resourceUrl: (0, rdf_1.documentResourceInput)(subject),
727
+ planId: validation.plan.id,
728
+ match: redactDescriptorObject(descriptor, row.match ?? {}),
729
+ set: redactDescriptorObject(descriptor, row.set ?? {}),
730
+ };
731
+ }
732
+ async function executeRows(input) {
733
+ const context = await (0, auth_context_1.requireAuthContext)(input.argv);
734
+ const items = [];
735
+ for (const [index, originalRow] of input.rows.entries()) {
736
+ const row = {
737
+ ...originalRow,
738
+ schema: originalRow.schema ?? input.defaultSchema,
739
+ };
740
+ try {
741
+ const item = row.schema
742
+ ? await executeDescriptorRow(context, row, index, input.commit)
743
+ : await executeRawRow(context, row, index, input.commit);
744
+ items.push(item);
745
+ }
746
+ catch (error) {
747
+ const err = error instanceof output_1.CliCommandError
748
+ ? error
749
+ : new output_1.CliCommandError('error', error instanceof Error ? error.message : String(error));
750
+ items.push({ index, ok: false, code: err.code, message: err.message });
751
+ }
752
+ }
753
+ return items;
754
+ }
755
+ function printItems(items) {
756
+ for (const item of items) {
757
+ console.log(`${item.index}\t${item.ok ? item.code : `ERROR ${item.code}`}\t${String(item.subject ?? item.resourceUrl ?? item.message ?? '')}`);
758
+ }
759
+ }
760
+ const importCommand = {
761
+ command: 'import <file>',
762
+ describe: 'Import descriptor-backed or explicit raw JSONL rows',
763
+ builder: (yargs) => objOptions(yargs)
764
+ .positional('file', { type: 'string', demandOption: true, description: 'JSONL file to import, or - for stdin' })
765
+ .option('dry-run', { type: 'boolean', description: 'Validate and print a plan without writing' })
766
+ .option('commit', { type: 'boolean', description: 'Commit the validated mutations' })
767
+ .check(mutationModeCheck),
768
+ handler: async (argv) => {
769
+ try {
770
+ const items = await executeRows({
771
+ argv,
772
+ rows: await readJsonl(argv.file),
773
+ commit: argv.commit === true,
774
+ });
775
+ const code = items.every((item) => item.ok)
776
+ ? (argv.commit ? 'committed' : 'plan_ready')
777
+ : 'partial_failure';
778
+ if (argv.json) {
779
+ (0, output_1.writeJsonItems)(items, code);
780
+ return;
781
+ }
782
+ printItems(items);
783
+ if (!items.every((item) => item.ok))
784
+ process.exit(1);
785
+ }
786
+ catch (error) {
787
+ (0, output_1.handleCliError)(error, argv.json);
788
+ }
789
+ },
790
+ };
791
+ const upsertCommand = {
792
+ command: 'upsert',
793
+ describe: 'Upsert descriptor-backed JSONL rows',
794
+ builder: (yargs) => objOptions(yargs)
795
+ .option('schema', { type: 'string', demandOption: true, description: 'Schema URI or descriptor alias' })
796
+ .option('from', { type: 'string', demandOption: true, description: 'JSONL file to read, or - for stdin' })
797
+ .option('dry-run', { type: 'boolean', description: 'Validate and print a plan without writing' })
798
+ .option('commit', { type: 'boolean', description: 'Commit the validated mutations' })
799
+ .check(mutationModeCheck),
800
+ handler: async (argv) => {
801
+ try {
802
+ const items = await executeRows({
803
+ argv,
804
+ rows: await readJsonl(argv.from),
805
+ defaultSchema: resolveDescriptor(argv.schema).uri,
806
+ commit: argv.commit === true,
807
+ });
808
+ const code = items.every((item) => item.ok)
809
+ ? (argv.commit ? 'committed' : 'plan_ready')
810
+ : 'partial_failure';
811
+ if (argv.json) {
812
+ (0, output_1.writeJsonItems)(items, code);
813
+ return;
814
+ }
815
+ printItems(items);
816
+ if (!items.every((item) => item.ok))
817
+ process.exit(1);
818
+ }
819
+ catch (error) {
820
+ (0, output_1.handleCliError)(error, argv.json);
821
+ }
822
+ },
823
+ };
824
+ const listCommand = {
825
+ command: 'list',
826
+ describe: 'List descriptor-backed objects',
827
+ builder: (yargs) => selectorOptions(yargs)
828
+ .option('schema', { type: 'string', demandOption: true, description: 'Schema URI or descriptor alias' }),
829
+ handler: async (argv) => {
830
+ try {
831
+ const context = await (0, auth_context_1.requireAuthContext)(argv);
832
+ const selector = await resolveObjectSelector(context, argv);
833
+ const objects = await queryDescriptorObjects(context, selector);
834
+ if (argv.json) {
835
+ (0, output_1.writeJsonResult)({
836
+ webId: context.webId,
837
+ podRoot: context.podRoot,
838
+ schema: selector.descriptor.uri,
839
+ objects,
840
+ });
841
+ return;
842
+ }
843
+ for (const object of objects) {
844
+ console.log(object.subject);
845
+ }
846
+ }
847
+ catch (error) {
848
+ (0, output_1.handleCliError)(error, argv.json);
849
+ }
850
+ },
851
+ };
852
+ const exportCommand = {
853
+ command: 'export <selector>',
854
+ describe: 'Export descriptor-backed objects as JSONL',
855
+ builder: (yargs) => selectorOptions(yargs)
856
+ .positional('selector', { type: 'string', demandOption: true, description: 'Schema URI/alias, subject, resource URL, or Pod-relative path selector' })
857
+ .option('format', { type: 'string', choices: ['jsonl'], default: 'jsonl', description: 'Export format' })
858
+ .option('out', { type: 'string', description: 'Output file path' }),
859
+ handler: async (argv) => {
860
+ try {
861
+ const context = await (0, auth_context_1.requireAuthContext)(argv);
862
+ const selector = await resolveObjectSelector(context, argv);
863
+ const objects = await queryDescriptorObjects(context, selector);
864
+ const jsonl = jsonlForObjects(objects);
865
+ if (argv.out) {
866
+ (0, fs_1.writeFileSync)(argv.out, jsonl, 'utf-8');
867
+ }
868
+ if (argv.json) {
869
+ (0, output_1.writeJsonResult)({
870
+ webId: context.webId,
871
+ podRoot: context.podRoot,
872
+ schema: selector.descriptor.uri,
873
+ count: objects.length,
874
+ out: argv.out ?? null,
875
+ ...(argv.out ? {} : { objects }),
876
+ });
877
+ return;
878
+ }
879
+ if (!argv.out) {
880
+ process.stdout.write(jsonl);
881
+ }
882
+ }
883
+ catch (error) {
884
+ (0, output_1.handleCliError)(error, argv.json);
885
+ }
886
+ },
887
+ };
888
+ const getCommand = {
889
+ command: 'get',
890
+ describe: 'Read one descriptor-backed object as JSON',
891
+ builder: (yargs) => objOptions(yargs)
892
+ .option('schema', { type: 'string', description: 'Schema URI or descriptor alias' })
893
+ .option('subject', { type: 'string', demandOption: true, description: 'Object subject URI or Pod-relative subject' })
894
+ .option('out', { type: 'string', description: 'Write JSON object to file' })
895
+ .option('include-metadata', { type: 'boolean', default: true, description: 'Include etag/revision metadata when available' }),
896
+ handler: async (argv) => {
897
+ try {
898
+ const context = await (0, auth_context_1.requireAuthContext)(argv);
899
+ const selector = await resolveObjectSelector(context, {
900
+ ...argv,
901
+ selector: undefined,
902
+ limit: 1,
903
+ format: 'jsonl',
904
+ where: undefined,
905
+ relation: undefined,
906
+ status: undefined,
907
+ });
908
+ const objects = await queryDescriptorObjects(context, selector);
909
+ const object = objects[0];
910
+ if (!object) {
911
+ throw new output_1.CliCommandError('object_not_found', `Object not found: ${argv.subject}`, 1);
912
+ }
913
+ const body = `${JSON.stringify(object, null, 2)}\n`;
914
+ if (argv.out) {
915
+ (0, fs_1.writeFileSync)(argv.out, body, 'utf-8');
916
+ }
917
+ if (argv.json) {
918
+ (0, output_1.writeJsonResult)({ webId: context.webId, podRoot: context.podRoot, object, out: argv.out ?? null });
919
+ return;
920
+ }
921
+ if (!argv.out) {
922
+ process.stdout.write(body);
923
+ }
924
+ }
925
+ catch (error) {
926
+ (0, output_1.handleCliError)(error, argv.json);
927
+ }
928
+ },
929
+ };
930
+ const patchCommand = {
931
+ command: 'patch',
932
+ describe: 'Patch one descriptor-backed object field set',
933
+ builder: (yargs) => objOptions(yargs)
934
+ .option('schema', { type: 'string', description: 'Schema URI or descriptor alias. Required if the subject document cannot be inferred.' })
935
+ .option('subject', { type: 'string', demandOption: true, description: 'Object subject URI or Pod-relative subject' })
936
+ .option('set', { type: 'string', demandOption: true, description: 'JSON object of descriptor field values to set' })
937
+ .option('if-match', { type: 'string', description: 'If-Match header for stale-write protection' })
938
+ .option('dry-run', { type: 'boolean', description: 'Print the mutation plan without writing' })
939
+ .option('commit', { type: 'boolean', description: 'Commit the mutation' })
940
+ .check(mutationModeCheck),
941
+ handler: async (argv) => {
942
+ try {
943
+ const data = await executePatchCommand(argv);
944
+ if (argv.json) {
945
+ (0, output_1.writeJsonResult)(data, argv['dry-run'] ? 'plan_ready' : 'committed');
946
+ return;
947
+ }
948
+ console.log(argv['dry-run'] ? JSON.stringify(data.plan, null, 2) : `PATCH ${String(data.resourceUrl)} -> HTTP ${String(data.status)}`);
949
+ }
950
+ catch (error) {
951
+ (0, output_1.handleCliError)(error, argv.json);
952
+ }
953
+ },
954
+ };
955
+ const linkCommand = {
956
+ command: 'link',
957
+ describe: 'Link one descriptor-backed object to another URI',
958
+ builder: (yargs) => objOptions(yargs)
959
+ .option('schema', { type: 'string', description: 'Schema URI or descriptor alias when --predicate is a descriptor field' })
960
+ .option('subject', { type: 'string', demandOption: true, description: 'Object subject URI or Pod-relative subject' })
961
+ .option('predicate', { type: 'string', demandOption: true, description: 'Predicate URI or descriptor field' })
962
+ .option('object', { type: 'string', demandOption: true, description: 'Object URI to link' })
963
+ .option('if-match', { type: 'string', description: 'If-Match header for stale-write protection' })
964
+ .option('dry-run', { type: 'boolean', description: 'Print the mutation plan without writing' })
965
+ .option('commit', { type: 'boolean', description: 'Commit the mutation' })
966
+ .check(mutationModeCheck),
967
+ handler: async (argv) => {
968
+ try {
969
+ const data = await executeLinkCommand(argv);
970
+ if (argv.json) {
971
+ (0, output_1.writeJsonResult)(data, argv['dry-run'] ? 'plan_ready' : 'committed');
972
+ return;
973
+ }
974
+ console.log(argv['dry-run'] ? JSON.stringify(data.plan, null, 2) : `LINK ${String(data.resourceUrl)} -> HTTP ${String(data.status)}`);
975
+ }
976
+ catch (error) {
977
+ (0, output_1.handleCliError)(error, argv.json);
978
+ }
979
+ },
980
+ };
981
+ const deleteCommand = {
982
+ command: 'delete',
983
+ describe: 'Delete triples for one descriptor-backed object subject',
984
+ builder: (yargs) => objOptions(yargs)
985
+ .option('schema', { type: 'string', description: 'Schema URI or descriptor alias. Required if the subject document cannot be inferred.' })
986
+ .option('subject', { type: 'string', demandOption: true, description: 'Object subject URI or Pod-relative subject' })
987
+ .option('if-match', { type: 'string', description: 'If-Match header for stale-write protection' })
988
+ .option('dry-run', { type: 'boolean', description: 'Print the mutation plan without writing' })
989
+ .option('commit', { type: 'boolean', description: 'Commit the mutation' })
990
+ .check(mutationModeCheck),
991
+ handler: async (argv) => {
992
+ try {
993
+ const data = await executeDeleteCommand(argv);
994
+ if (argv.json) {
995
+ (0, output_1.writeJsonResult)(data, argv['dry-run'] ? 'plan_ready' : 'committed');
996
+ return;
997
+ }
998
+ console.log(argv['dry-run'] ? JSON.stringify(data.plan, null, 2) : `DELETE ${String(data.resourceUrl)} -> HTTP ${String(data.status)}`);
999
+ }
1000
+ catch (error) {
1001
+ (0, output_1.handleCliError)(error, argv.json);
1002
+ }
1003
+ },
1004
+ };
1005
+ const watchCommand = {
1006
+ command: 'watch <selector>',
1007
+ describe: 'Stream descriptor-backed object changes as JSONL snapshots',
1008
+ builder: (yargs) => selectorOptions(yargs)
1009
+ .positional('selector', { type: 'string', demandOption: true, description: 'Schema URI/alias, subject, resource URL, or Pod-relative path selector' })
1010
+ .option('format', { type: 'string', choices: ['jsonl'], default: 'jsonl', description: 'Watch stream format' })
1011
+ .option('since', { type: 'string', description: 'Opaque cursor from a previous watch stream' }),
1012
+ handler: async (argv) => {
1013
+ try {
1014
+ const context = await (0, auth_context_1.requireAuthContext)(argv);
1015
+ const selector = await resolveObjectSelector(context, argv);
1016
+ const objects = await queryDescriptorObjects(context, selector);
1017
+ const metadata = {
1018
+ cursorDurable: false,
1019
+ since: argv.since ?? null,
1020
+ cursor: null,
1021
+ message: 'Durable object cursors are not available; reconcile with a fresh obj list/export snapshot.',
1022
+ };
1023
+ if (argv.json) {
1024
+ (0, output_1.writeJsonResult)({ metadata, objects }, 'watch_snapshot');
1025
+ return;
1026
+ }
1027
+ console.log(JSON.stringify({ ok: true, code: 'cursor_unavailable', metadata }));
1028
+ for (const [index, object] of objects.entries()) {
1029
+ console.log(JSON.stringify({
1030
+ index,
1031
+ ok: true,
1032
+ code: 'snapshot',
1033
+ change: 'snapshot',
1034
+ ...object,
1035
+ }));
1036
+ }
1037
+ }
1038
+ catch (error) {
1039
+ (0, output_1.handleCliError)(error, argv.json);
1040
+ }
1041
+ },
1042
+ };
1043
+ exports.objCommand = {
1044
+ command: 'obj',
1045
+ describe: 'Descriptor-backed object transport',
1046
+ builder: (yargs) => yargs
1047
+ .command(exportCommand)
1048
+ .command(importCommand)
1049
+ .command(getCommand)
1050
+ .command(listCommand)
1051
+ .command(upsertCommand)
1052
+ .command(patchCommand)
1053
+ .command(linkCommand)
1054
+ .command(deleteCommand)
1055
+ .command(watchCommand)
1056
+ .demandCommand(1, 'Please specify an object subcommand'),
1057
+ handler: () => { },
1058
+ };
1059
+ //# sourceMappingURL=obj.js.map