@artkit-ai/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +119 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +45 -0
- package/dist/adapter.js.map +1 -0
- package/dist/diff.d.ts +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/engine.d.ts +29 -23
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +148 -197
- package/dist/engine.js.map +1 -1
- package/dist/envelope.d.ts +10 -27
- package/dist/envelope.d.ts.map +1 -1
- package/dist/envelope.js +8 -20
- package/dist/envelope.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -4
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +2 -10
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/i18n.d.ts +8 -14
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +12 -18
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/memory.d.ts +0 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +15 -109
- package/dist/memory.js.map +1 -1
- package/dist/migrations.d.ts +6 -5
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +20 -14
- package/dist/migrations.js.map +1 -1
- package/dist/projection.d.ts +22 -6
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +5 -5
- package/dist/projection.js.map +1 -1
- package/dist/registry.d.ts +17 -16
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +25 -25
- package/dist/registry.js.map +1 -1
- package/dist/service.d.ts +19 -51
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +119 -404
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +12 -61
- package/dist/store.d.ts.map +1 -1
- package/dist/testing/index.d.ts +10 -9
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +75 -136
- package/dist/testing/index.js.map +1 -1
- package/dist/theme.js +1 -1
- package/dist/ui.d.ts +18 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +5 -3
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter.ts +180 -0
- package/src/diff.ts +1 -1
- package/src/engine.ts +183 -230
- package/src/envelope.ts +11 -29
- package/src/errors.ts +4 -4
- package/src/events.ts +2 -11
- package/src/i18n.ts +14 -20
- package/src/index.ts +2 -4
- package/src/logger.ts +1 -2
- package/src/memory.ts +16 -122
- package/src/migrations.ts +24 -18
- package/src/projection.ts +25 -7
- package/src/registry.ts +45 -41
- package/src/service.ts +144 -540
- package/src/store.ts +12 -64
- package/src/testing/index.ts +89 -164
- package/src/theme.ts +1 -1
- package/src/ui.ts +24 -4
- package/dist/kind.d.ts +0 -156
- package/dist/kind.d.ts.map +0 -1
- package/dist/kind.js +0 -52
- package/dist/kind.js.map +0 -1
- package/src/kind.ts +0 -217
package/src/errors.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { t } from './i18n.js';
|
|
|
3
3
|
|
|
4
4
|
/** Error catalog (spec §10.3). `INTERNAL_ERROR` is the only extension: unexpected failures never leak details. */
|
|
5
5
|
export const ERROR_CODES = [
|
|
6
|
-
'
|
|
7
|
-
'
|
|
6
|
+
'UNKNOWN_FORMAT',
|
|
7
|
+
'DUPLICATE_FORMAT',
|
|
8
8
|
'NOT_FOUND',
|
|
9
9
|
'INVALID_ENVELOPE',
|
|
10
|
-
'
|
|
10
|
+
'UNSUPPORTED_FORMAT_VERSION',
|
|
11
11
|
'MIGRATION_FAILED',
|
|
12
12
|
'INVALID_CONTENT',
|
|
13
13
|
'INVALID_OPERATION',
|
|
@@ -16,9 +16,9 @@ export const ERROR_CODES = [
|
|
|
16
16
|
'LOCKED',
|
|
17
17
|
'LIMIT_EXCEEDED',
|
|
18
18
|
'PERMISSION_DENIED',
|
|
19
|
-
'APPROVAL_REQUIRED',
|
|
20
19
|
'ASSET_REJECTED',
|
|
21
20
|
'EXPORT_FAILED',
|
|
21
|
+
'ENGINE_UNAVAILABLE',
|
|
22
22
|
'INTERNAL_ERROR',
|
|
23
23
|
] as const;
|
|
24
24
|
|
package/src/events.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { ChangeSummary } from './adapter.js';
|
|
2
|
+
import type { Actor, ArtifactSummary } from './envelope.js';
|
|
3
3
|
|
|
4
4
|
/** Typed events (spec §15.4). They carry metadata only, never content. */
|
|
5
5
|
export type ArtifactEvent =
|
|
@@ -10,7 +10,6 @@ export type ArtifactEvent =
|
|
|
10
10
|
actor: Actor;
|
|
11
11
|
operationCount: number;
|
|
12
12
|
diff: ChangeSummary[];
|
|
13
|
-
rebased: boolean;
|
|
14
13
|
}
|
|
15
14
|
| {
|
|
16
15
|
type: 'artifact.conflict';
|
|
@@ -19,14 +18,6 @@ export type ArtifactEvent =
|
|
|
19
18
|
currentVersion: number;
|
|
20
19
|
actor: Actor;
|
|
21
20
|
}
|
|
22
|
-
| {
|
|
23
|
-
type: 'artifact.status_changed';
|
|
24
|
-
artifact: ArtifactSummary;
|
|
25
|
-
from: ArtifactStatus;
|
|
26
|
-
to: ArtifactStatus;
|
|
27
|
-
actor: Actor;
|
|
28
|
-
message?: string;
|
|
29
|
-
}
|
|
30
21
|
| { type: 'artifact.exported'; artifact: ArtifactSummary; format: string; actor: Actor; warnings: number }
|
|
31
22
|
| { type: 'artifact.lint'; artifactId: string; version: number; warnings: number };
|
|
32
23
|
|
package/src/i18n.ts
CHANGED
|
@@ -9,14 +9,14 @@ type Template = (params: Record<string, unknown>) => string;
|
|
|
9
9
|
|
|
10
10
|
const messages = {
|
|
11
11
|
'pt-BR': {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
UNKNOWN_FORMAT: (p) => `O formato "${p.format}" não está registrado.`,
|
|
13
|
+
UNKNOWN_FORMAT_HINT: (p) => `Use um dos formatos disponíveis: ${p.available}.`,
|
|
14
|
+
DUPLICATE_FORMAT: (p) => `O formato "${p.format}" já está registrado.`,
|
|
15
15
|
NOT_FOUND: (p) => `${p.what} não encontrado: ${p.id}.`,
|
|
16
16
|
NOT_FOUND_HINT: () => 'Confira o id com artifact_list ou leia o artefato novamente.',
|
|
17
17
|
INVALID_ENVELOPE: () => 'O envelope do artefato é inválido.',
|
|
18
|
-
|
|
19
|
-
`O conteúdo usa a versão de
|
|
18
|
+
UNSUPPORTED_FORMAT_VERSION: (p) =>
|
|
19
|
+
`O conteúdo usa a versão de formato ${p.found}, mas o formato "${p.format}" lê apenas ${p.supported}.`,
|
|
20
20
|
MIGRATION_FAILED: (p) => `Falha ao migrar o conteúdo da versão ${p.from} para a ${p.to}.`,
|
|
21
21
|
INVALID_CONTENT: () => 'O conteúdo é inválido.',
|
|
22
22
|
INVALID_OPERATION: (p) => `A operação ${p.index} é inválida.`,
|
|
@@ -30,15 +30,12 @@ const messages = {
|
|
|
30
30
|
LIMIT_EXCEEDED: (p) => `Limite "${p.limit}" excedido: máximo ${p.max}, encontrado ${p.found}.`,
|
|
31
31
|
LIMIT_EXCEEDED_HINT: () => 'Divida o conteúdo em mais artefatos ou reduza a quantidade de itens.',
|
|
32
32
|
PERMISSION_DENIED: (p) => `Ação "${p.action}" negada.`,
|
|
33
|
-
APPROVAL_REQUIRED: (p) => `A ação "${p.action}" depende de aprovação humana.`,
|
|
34
|
-
APPROVAL_REQUIRED_HINT: () => 'Use artifact_submit para enviar o artefato para aprovação.',
|
|
35
33
|
ASSET_REJECTED: (p) => `Asset rejeitado: ${p.reason}.`,
|
|
36
34
|
EXPORT_FAILED: (p) => `Falha ao exportar para ${p.format}.`,
|
|
37
35
|
INTERNAL_ERROR: () => 'Erro interno ao processar a solicitação.',
|
|
38
|
-
|
|
39
|
-
INPUT_FORMAT_UNSUPPORTED: (p) => `O tipo "${p.kind}" não aceita a entrada "${p.format}".`,
|
|
36
|
+
INPUT_FORMAT_UNSUPPORTED: (p) => `O formato "${p.format}" não aceita a entrada "${p.input}".`,
|
|
40
37
|
INPUT_FORMAT_HINT: (p) => `Formatos aceitos: ${p.available}.`,
|
|
41
|
-
EXPORTER_NOT_FOUND: (p) => `Não há exportador de "${p.
|
|
38
|
+
EXPORTER_NOT_FOUND: (p) => `Não há exportador de "${p.format}" para o formato "${p.target}".`,
|
|
42
39
|
THEME_NOT_FOUND: (p) => `Tema "${p.themeId}" não registrado.`,
|
|
43
40
|
DUPLICATE_ID: (p) => `O id "${p.id}" está repetido.`,
|
|
44
41
|
DUPLICATE_ID_HINT: () => 'Cada item precisa de um id único dentro do artefato.',
|
|
@@ -62,14 +59,14 @@ const messages = {
|
|
|
62
59
|
ZOD_CUSTOM: (p) => String(p.message ?? 'Valor inválido.'),
|
|
63
60
|
},
|
|
64
61
|
en: {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
UNKNOWN_FORMAT: (p) => `Format "${p.format}" is not registered.`,
|
|
63
|
+
UNKNOWN_FORMAT_HINT: (p) => `Use one of the available formats: ${p.available}.`,
|
|
64
|
+
DUPLICATE_FORMAT: (p) => `Format "${p.format}" is already registered.`,
|
|
68
65
|
NOT_FOUND: (p) => `${p.what} not found: ${p.id}.`,
|
|
69
66
|
NOT_FOUND_HINT: () => 'Check the id with artifact_list or read the artifact again.',
|
|
70
67
|
INVALID_ENVELOPE: () => 'The artifact envelope is invalid.',
|
|
71
|
-
|
|
72
|
-
`Content uses
|
|
68
|
+
UNSUPPORTED_FORMAT_VERSION: (p) =>
|
|
69
|
+
`Content uses format version ${p.found}, but format "${p.format}" reads only ${p.supported}.`,
|
|
73
70
|
MIGRATION_FAILED: (p) => `Failed to migrate content from version ${p.from} to ${p.to}.`,
|
|
74
71
|
INVALID_CONTENT: () => 'The content is invalid.',
|
|
75
72
|
INVALID_OPERATION: (p) => `Operation ${p.index} is invalid.`,
|
|
@@ -83,15 +80,12 @@ const messages = {
|
|
|
83
80
|
LIMIT_EXCEEDED: (p) => `Limit "${p.limit}" exceeded: max ${p.max}, found ${p.found}.`,
|
|
84
81
|
LIMIT_EXCEEDED_HINT: () => 'Split the content into more artifacts or reduce the number of items.',
|
|
85
82
|
PERMISSION_DENIED: (p) => `Action "${p.action}" denied.`,
|
|
86
|
-
APPROVAL_REQUIRED: (p) => `Action "${p.action}" requires human approval.`,
|
|
87
|
-
APPROVAL_REQUIRED_HINT: () => 'Use artifact_submit to send the artifact for approval.',
|
|
88
83
|
ASSET_REJECTED: (p) => `Asset rejected: ${p.reason}.`,
|
|
89
84
|
EXPORT_FAILED: (p) => `Failed to export to ${p.format}.`,
|
|
90
85
|
INTERNAL_ERROR: () => 'Internal error while processing the request.',
|
|
91
|
-
|
|
92
|
-
INPUT_FORMAT_UNSUPPORTED: (p) => `Kind "${p.kind}" does not accept "${p.format}" input.`,
|
|
86
|
+
INPUT_FORMAT_UNSUPPORTED: (p) => `Format "${p.format}" does not accept "${p.input}" input.`,
|
|
93
87
|
INPUT_FORMAT_HINT: (p) => `Accepted formats: ${p.available}.`,
|
|
94
|
-
EXPORTER_NOT_FOUND: (p) => `No "${p.
|
|
88
|
+
EXPORTER_NOT_FOUND: (p) => `No exporter from "${p.format}" to "${p.target}".`,
|
|
95
89
|
THEME_NOT_FOUND: (p) => `Theme "${p.themeId}" is not registered.`,
|
|
96
90
|
DUPLICATE_ID: (p) => `Id "${p.id}" is duplicated.`,
|
|
97
91
|
DUPLICATE_ID_HINT: () => 'Every item needs an id that is unique inside the artifact.',
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './adapter.js';
|
|
1
2
|
export * from './assets.js';
|
|
2
3
|
export * from './diff.js';
|
|
3
4
|
export * from './engine.js';
|
|
@@ -7,7 +8,6 @@ export * from './events.js';
|
|
|
7
8
|
export * from './i18n.js';
|
|
8
9
|
export * from './ids.js';
|
|
9
10
|
export * from './jsonschema.js';
|
|
10
|
-
export * from './kind.js';
|
|
11
11
|
export * from './logger.js';
|
|
12
12
|
export * from './memory.js';
|
|
13
13
|
export * from './migrations.js';
|
|
@@ -16,18 +16,16 @@ export * from './registry.js';
|
|
|
16
16
|
export * from './sanitize.js';
|
|
17
17
|
export {
|
|
18
18
|
type ApplyRequest,
|
|
19
|
-
type ApprovalPolicy,
|
|
20
19
|
type ArtifactAction,
|
|
20
|
+
type ArtifactReadResult,
|
|
21
21
|
type ArtifactService,
|
|
22
22
|
type ArtifactServiceOptions,
|
|
23
23
|
type AuthorizeFn,
|
|
24
24
|
type CommitResult,
|
|
25
25
|
type CreateRequest,
|
|
26
26
|
createArtifactService,
|
|
27
|
-
DEFAULT_APPROVAL_POLICY,
|
|
28
27
|
type ExportRequest,
|
|
29
28
|
type ExportResult,
|
|
30
|
-
type ReadResult,
|
|
31
29
|
slugify,
|
|
32
30
|
} from './service.js';
|
|
33
31
|
export * from './store.js';
|
package/src/logger.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/** Structured, injectable logger (spec §17). Never log artifact content, cell text, prompts or personal data. */
|
|
2
2
|
export interface LogFields {
|
|
3
3
|
artifactId?: string;
|
|
4
|
-
|
|
4
|
+
format?: string;
|
|
5
5
|
version?: number;
|
|
6
6
|
operationCount?: number;
|
|
7
7
|
durationMs?: number;
|
|
8
8
|
code?: string;
|
|
9
9
|
actorType?: string;
|
|
10
|
-
format?: string;
|
|
11
10
|
[key: string]: string | number | boolean | undefined;
|
|
12
11
|
}
|
|
13
12
|
|
package/src/memory.ts
CHANGED
|
@@ -7,13 +7,7 @@ import {
|
|
|
7
7
|
sha256Hex,
|
|
8
8
|
toDataUrl,
|
|
9
9
|
} from './assets.js';
|
|
10
|
-
import {
|
|
11
|
-
type Actor,
|
|
12
|
-
type Artifact,
|
|
13
|
-
type ArtifactStatus,
|
|
14
|
-
type ArtifactSummary,
|
|
15
|
-
summarize,
|
|
16
|
-
} from './envelope.js';
|
|
10
|
+
import { type Artifact, type ArtifactSummary, summarize } from './envelope.js';
|
|
17
11
|
import { ArtifactError } from './errors.js';
|
|
18
12
|
import { t } from './i18n.js';
|
|
19
13
|
import { createArtifactId } from './ids.js';
|
|
@@ -27,31 +21,24 @@ import type {
|
|
|
27
21
|
Page,
|
|
28
22
|
PageRequest,
|
|
29
23
|
Scope,
|
|
30
|
-
VersionEntry,
|
|
31
24
|
} from './store.js';
|
|
32
25
|
|
|
33
26
|
export interface MemoryStoreOptions {
|
|
34
27
|
now?: () => Date;
|
|
35
|
-
/** Snapshot every N versions (first version always has one). Memory keeps full versions anyway. */
|
|
36
|
-
snapshotEvery?: number;
|
|
37
28
|
locale?: string;
|
|
38
29
|
}
|
|
39
30
|
|
|
40
31
|
interface Record_ {
|
|
41
32
|
workspaceId: string;
|
|
42
|
-
|
|
43
|
-
history: VersionEntry[];
|
|
33
|
+
artifact: Artifact;
|
|
44
34
|
lock?: Lock;
|
|
45
35
|
}
|
|
46
36
|
|
|
47
37
|
const DEFAULT_PAGE = 50;
|
|
48
38
|
|
|
49
|
-
const sameActor = (a: Actor, b: Actor) => a.type === b.type && a.id === b.id;
|
|
50
|
-
|
|
51
39
|
/** Reference in-memory store: tests, playground and prototypes (spec §15.2). Runs in any runtime. */
|
|
52
40
|
export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactStore {
|
|
53
41
|
const now = options.now ?? (() => new Date());
|
|
54
|
-
const snapshotEvery = Math.max(1, options.snapshotEvery ?? 10);
|
|
55
42
|
const records = new Map<string, Record_>();
|
|
56
43
|
|
|
57
44
|
const notFound = (id: string): never => {
|
|
@@ -65,10 +52,6 @@ export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactSto
|
|
|
65
52
|
return found;
|
|
66
53
|
}
|
|
67
54
|
|
|
68
|
-
function latest(rec: Record_): Artifact {
|
|
69
|
-
return rec.versions[rec.versions.length - 1]!;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
55
|
function activeLock(rec: Record_): Lock | null {
|
|
73
56
|
if (!rec.lock) return null;
|
|
74
57
|
if (Date.parse(rec.lock.expiresAt) <= now().getTime()) {
|
|
@@ -90,12 +73,10 @@ export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactSto
|
|
|
90
73
|
const clone = <T>(value: T): T => structuredClone(value);
|
|
91
74
|
|
|
92
75
|
return {
|
|
93
|
-
async get(scope, id
|
|
76
|
+
async get(scope, id) {
|
|
94
77
|
const rec = records.get(id);
|
|
95
78
|
if (!rec || rec.workspaceId !== scope.workspaceId) return null;
|
|
96
|
-
|
|
97
|
-
const found = rec.versions.find((v) => v.version === opts.version);
|
|
98
|
-
return found ? clone(found) : null;
|
|
79
|
+
return clone(rec.artifact);
|
|
99
80
|
},
|
|
100
81
|
|
|
101
82
|
async create(scope, input: NewArtifact) {
|
|
@@ -103,11 +84,10 @@ export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactSto
|
|
|
103
84
|
const id = createArtifactId(at.getTime());
|
|
104
85
|
const artifact: Artifact = {
|
|
105
86
|
id,
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
format: input.format,
|
|
88
|
+
formatVersion: input.formatVersion,
|
|
108
89
|
version: 1,
|
|
109
90
|
title: input.title,
|
|
110
|
-
status: input.status,
|
|
111
91
|
locale: input.locale,
|
|
112
92
|
themeId: input.themeId,
|
|
113
93
|
content: clone(input.content),
|
|
@@ -119,30 +99,13 @@ export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactSto
|
|
|
119
99
|
tags: input.tags ?? [],
|
|
120
100
|
},
|
|
121
101
|
};
|
|
122
|
-
records.set(id, {
|
|
123
|
-
workspaceId: scope.workspaceId,
|
|
124
|
-
versions: [artifact],
|
|
125
|
-
history: [
|
|
126
|
-
{
|
|
127
|
-
version: 1,
|
|
128
|
-
actor: scope.actor,
|
|
129
|
-
createdAt: at.toISOString(),
|
|
130
|
-
operations: [],
|
|
131
|
-
diff: [],
|
|
132
|
-
createdIds: input.createdIds ?? [],
|
|
133
|
-
status: input.status,
|
|
134
|
-
...(input.message ? { message: input.message } : {}),
|
|
135
|
-
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
136
|
-
snapshot: { schemaVersion: input.schemaVersion, content: clone(input.content) },
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
});
|
|
102
|
+
records.set(id, { workspaceId: scope.workspaceId, artifact });
|
|
140
103
|
return clone(artifact);
|
|
141
104
|
},
|
|
142
105
|
|
|
143
106
|
async commit(scope, id, change: Commit) {
|
|
144
107
|
const rec = record(scope, id);
|
|
145
|
-
const current =
|
|
108
|
+
const current = rec.artifact;
|
|
146
109
|
if (change.baseVersion !== current.version) {
|
|
147
110
|
throw new ArtifactError(
|
|
148
111
|
'VERSION_CONFLICT',
|
|
@@ -150,55 +113,29 @@ export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactSto
|
|
|
150
113
|
baseVersion: change.baseVersion,
|
|
151
114
|
currentVersion: current.version,
|
|
152
115
|
}),
|
|
153
|
-
{
|
|
154
|
-
details: { currentVersion: current.version },
|
|
155
|
-
},
|
|
116
|
+
{ details: { currentVersion: current.version } },
|
|
156
117
|
);
|
|
157
118
|
}
|
|
158
|
-
const at = now().toISOString();
|
|
159
|
-
const version = current.version + 1;
|
|
160
119
|
const next: Artifact = {
|
|
161
120
|
...current,
|
|
162
|
-
|
|
163
|
-
version,
|
|
121
|
+
formatVersion: change.formatVersion,
|
|
122
|
+
version: current.version + 1,
|
|
164
123
|
title: change.title,
|
|
165
124
|
locale: change.locale,
|
|
166
125
|
themeId: change.themeId,
|
|
167
|
-
status: change.status ?? current.status,
|
|
168
126
|
content: clone(change.content),
|
|
169
|
-
meta: { ...current.meta, updatedAt:
|
|
127
|
+
meta: { ...current.meta, updatedAt: now().toISOString(), updatedBy: scope.actor },
|
|
170
128
|
};
|
|
171
|
-
rec.
|
|
172
|
-
rec.history.push({
|
|
173
|
-
version,
|
|
174
|
-
actor: scope.actor,
|
|
175
|
-
createdAt: at,
|
|
176
|
-
operations: clone(change.operations),
|
|
177
|
-
diff: clone(change.diff),
|
|
178
|
-
createdIds: change.createdIds ?? [],
|
|
179
|
-
status: next.status,
|
|
180
|
-
...(change.message ? { message: change.message } : {}),
|
|
181
|
-
...(change.idempotencyKey ? { idempotencyKey: change.idempotencyKey } : {}),
|
|
182
|
-
...(change.restoredFrom ? { restoredFrom: change.restoredFrom } : {}),
|
|
183
|
-
...(version % snapshotEvery === 0
|
|
184
|
-
? { snapshot: { schemaVersion: change.schemaVersion, content: clone(change.content) } }
|
|
185
|
-
: {}),
|
|
186
|
-
});
|
|
129
|
+
rec.artifact = next;
|
|
187
130
|
return clone(next);
|
|
188
131
|
},
|
|
189
132
|
|
|
190
|
-
async history(scope, id, page) {
|
|
191
|
-
const rec = record(scope, id);
|
|
192
|
-
return paginate(clone([...rec.history].reverse()), page);
|
|
193
|
-
},
|
|
194
|
-
|
|
195
133
|
async list(scope, filter: ArtifactFilter, page) {
|
|
196
134
|
const items: ArtifactSummary[] = [];
|
|
197
135
|
for (const rec of records.values()) {
|
|
198
136
|
if (rec.workspaceId !== scope.workspaceId) continue;
|
|
199
|
-
const artifact =
|
|
200
|
-
if (filter.
|
|
201
|
-
if (filter.status && artifact.status !== filter.status) continue;
|
|
137
|
+
const artifact = rec.artifact;
|
|
138
|
+
if (filter.format && artifact.format !== filter.format) continue;
|
|
202
139
|
if (filter.title && !artifact.title.toLowerCase().includes(filter.title.toLowerCase())) continue;
|
|
203
140
|
if (filter.tag && !artifact.meta.tags.includes(filter.tag)) continue;
|
|
204
141
|
items.push(summarize(artifact));
|
|
@@ -209,32 +146,6 @@ export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactSto
|
|
|
209
146
|
return paginate(items, page);
|
|
210
147
|
},
|
|
211
148
|
|
|
212
|
-
async setStatus(scope, id, status: ArtifactStatus, actor: Actor, message?: string) {
|
|
213
|
-
const rec = record(scope, id);
|
|
214
|
-
const current = latest(rec);
|
|
215
|
-
const at = now().toISOString();
|
|
216
|
-
const updated: Artifact = {
|
|
217
|
-
...current,
|
|
218
|
-
status,
|
|
219
|
-
meta: {
|
|
220
|
-
...current.meta,
|
|
221
|
-
updatedAt: at,
|
|
222
|
-
updatedBy: actor,
|
|
223
|
-
...(status === 'published' ? { publishedVersion: current.version } : {}),
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
rec.versions[rec.versions.length - 1] = updated;
|
|
227
|
-
const entry = rec.history[rec.history.length - 1];
|
|
228
|
-
if (entry) {
|
|
229
|
-
entry.status = status;
|
|
230
|
-
entry.statusLog = [
|
|
231
|
-
...(entry.statusLog ?? []),
|
|
232
|
-
{ status, actor, at, ...(message ? { message } : {}) },
|
|
233
|
-
];
|
|
234
|
-
}
|
|
235
|
-
return clone(updated);
|
|
236
|
-
},
|
|
237
|
-
|
|
238
149
|
async lock(scope, id, actor, ttlMs) {
|
|
239
150
|
const rec = record(scope, id);
|
|
240
151
|
const existing = activeLock(rec);
|
|
@@ -245,9 +156,7 @@ export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactSto
|
|
|
245
156
|
holder: `${existing.holder.type}:${existing.holder.id}`,
|
|
246
157
|
expiresAt: existing.expiresAt,
|
|
247
158
|
}),
|
|
248
|
-
{
|
|
249
|
-
details: { expiresAt: existing.expiresAt },
|
|
250
|
-
},
|
|
159
|
+
{ details: { expiresAt: existing.expiresAt } },
|
|
251
160
|
);
|
|
252
161
|
}
|
|
253
162
|
const token = createArtifactId(now().getTime()).slice(4);
|
|
@@ -270,21 +179,6 @@ export function createMemoryStore(options: MemoryStoreOptions = {}): ArtifactSto
|
|
|
270
179
|
const lock = activeLock(rec);
|
|
271
180
|
return lock ? clone(lock) : null;
|
|
272
181
|
},
|
|
273
|
-
|
|
274
|
-
async findByIdempotencyKey(scope, id, key) {
|
|
275
|
-
const rec = record(scope, id);
|
|
276
|
-
const entry = rec.history.find((h) => h.idempotencyKey === key);
|
|
277
|
-
return entry ? clone(entry) : null;
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
async findCreatedByIdempotencyKey(scope, key) {
|
|
281
|
-
for (const rec of records.values()) {
|
|
282
|
-
if (rec.workspaceId !== scope.workspaceId) continue;
|
|
283
|
-
const first = rec.history[0];
|
|
284
|
-
if (first?.idempotencyKey === key && sameActor(first.actor, scope.actor)) return clone(latest(rec));
|
|
285
|
-
}
|
|
286
|
-
return null;
|
|
287
|
-
},
|
|
288
182
|
};
|
|
289
183
|
}
|
|
290
184
|
|
package/src/migrations.ts
CHANGED
|
@@ -1,40 +1,44 @@
|
|
|
1
|
+
import type { AnyFormatAdapter } from './adapter.js';
|
|
1
2
|
import { ArtifactError } from './errors.js';
|
|
2
3
|
import { t } from './i18n.js';
|
|
3
|
-
import type { AnyArtifactKind } from './kind.js';
|
|
4
4
|
|
|
5
5
|
export interface MigrationResult {
|
|
6
6
|
content: unknown;
|
|
7
|
-
|
|
7
|
+
formatVersion: string;
|
|
8
8
|
migrated: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Migrates content in memory along the
|
|
13
|
-
* Persisting the migrated
|
|
12
|
+
* Migrates content in memory along the adapter's migration chain (spec §7.2). Reading a version the adapter does not
|
|
13
|
+
* declare is refused with `UNSUPPORTED_FORMAT_VERSION`. Persisting the migrated content only happens on the next
|
|
14
|
+
* commit.
|
|
14
15
|
*/
|
|
15
16
|
export function migrateContent(
|
|
16
|
-
|
|
17
|
+
adapter: AnyFormatAdapter,
|
|
17
18
|
content: unknown,
|
|
18
|
-
|
|
19
|
+
formatVersion: string,
|
|
19
20
|
locale?: string,
|
|
20
21
|
): MigrationResult {
|
|
21
|
-
if (
|
|
22
|
+
if (formatVersion === adapter.formatVersion) return { content, formatVersion, migrated: false };
|
|
23
|
+
if (!adapter.readableVersions.includes(formatVersion)) {
|
|
22
24
|
throw new ArtifactError(
|
|
23
|
-
'
|
|
24
|
-
t(locale, '
|
|
25
|
-
|
|
25
|
+
'UNSUPPORTED_FORMAT_VERSION',
|
|
26
|
+
t(locale, 'UNSUPPORTED_FORMAT_VERSION', {
|
|
27
|
+
found: formatVersion,
|
|
28
|
+
supported: adapter.readableVersions.join(', '),
|
|
29
|
+
format: adapter.format,
|
|
30
|
+
}),
|
|
31
|
+
{ details: { found: formatVersion, supported: adapter.readableVersions } },
|
|
26
32
|
);
|
|
27
33
|
}
|
|
28
|
-
if (schemaVersion === kind.version) return { content, schemaVersion, migrated: false };
|
|
29
34
|
let current = content;
|
|
30
|
-
let version =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const step = chain.find((migration) => migration.from === version);
|
|
35
|
+
let version = formatVersion;
|
|
36
|
+
while (version !== adapter.formatVersion) {
|
|
37
|
+
const step = adapter.migrations.find((migration) => migration.from === version);
|
|
34
38
|
if (!step) {
|
|
35
39
|
throw new ArtifactError(
|
|
36
40
|
'MIGRATION_FAILED',
|
|
37
|
-
t(locale, 'MIGRATION_FAILED', { from: version, to:
|
|
41
|
+
t(locale, 'MIGRATION_FAILED', { from: version, to: adapter.formatVersion }),
|
|
38
42
|
);
|
|
39
43
|
}
|
|
40
44
|
try {
|
|
@@ -43,10 +47,12 @@ export function migrateContent(
|
|
|
43
47
|
throw new ArtifactError(
|
|
44
48
|
'MIGRATION_FAILED',
|
|
45
49
|
t(locale, 'MIGRATION_FAILED', { from: step.from, to: step.to }),
|
|
46
|
-
{
|
|
50
|
+
{
|
|
51
|
+
cause,
|
|
52
|
+
},
|
|
47
53
|
);
|
|
48
54
|
}
|
|
49
55
|
version = step.to;
|
|
50
56
|
}
|
|
51
|
-
return { content: current,
|
|
57
|
+
return { content: current, formatVersion: version, migrated: true };
|
|
52
58
|
}
|
package/src/projection.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReadMode } from './adapter.js';
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_CHARS_PER_TOKEN = 4;
|
|
4
4
|
|
|
5
|
+
export interface Projection {
|
|
6
|
+
text: string;
|
|
7
|
+
truncated: boolean;
|
|
8
|
+
/** Present when truncated: pass it back to continue reading. */
|
|
9
|
+
cursor?: string;
|
|
10
|
+
/** Outline of what was left out, so the model can jump with `range`. */
|
|
11
|
+
remainingOutline?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ProjectionOptions {
|
|
15
|
+
mode: ReadMode;
|
|
16
|
+
/** Format-specific range: anchors, A1 ranges, declaration names or 1-based positions ("3-5"). */
|
|
17
|
+
range?: string;
|
|
18
|
+
maxTokens?: number;
|
|
19
|
+
cursor?: string;
|
|
20
|
+
charsPerToken?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
5
23
|
export function estimateTokens(text: string, charsPerToken = DEFAULT_CHARS_PER_TOKEN): number {
|
|
6
24
|
return Math.ceil(text.length / Math.max(1, charsPerToken));
|
|
7
25
|
}
|
|
@@ -9,7 +27,7 @@ export function estimateTokens(text: string, charsPerToken = DEFAULT_CHARS_PER_T
|
|
|
9
27
|
/** One unit of projected text. Truncation only happens between items. */
|
|
10
28
|
export interface ProjectionItem {
|
|
11
29
|
id: string;
|
|
12
|
-
/** Full text for `full` and `range` modes. */
|
|
30
|
+
/** Full text for `full`, `section` and `range` modes. */
|
|
13
31
|
text: string;
|
|
14
32
|
/** One-line summary used for `outline` and for the remaining outline after truncation. */
|
|
15
33
|
outline: string;
|
|
@@ -32,8 +50,8 @@ function decodeCursor(cursor: string | undefined): number {
|
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
/**
|
|
35
|
-
* Joins items under a token budget (spec §
|
|
36
|
-
* boundary and a cursor plus the outline of the remaining items is returned.
|
|
53
|
+
* Joins items under a token budget (spec §10.2 of the reading rules). When the budget runs out, the text is cut at an
|
|
54
|
+
* item boundary and a cursor plus the outline of the remaining items is returned.
|
|
37
55
|
*/
|
|
38
56
|
export function paginate(items: ProjectionItem[], options: PaginateOptions = {}): Projection {
|
|
39
57
|
const start = Math.min(decodeCursor(options.cursor), items.length);
|
|
@@ -65,7 +83,7 @@ export function paginate(items: ProjectionItem[], options: PaginateOptions = {})
|
|
|
65
83
|
return { text: parts.join('\n'), truncated: false };
|
|
66
84
|
}
|
|
67
85
|
|
|
68
|
-
/** Wraps a projection between explicit data markers so the model treats it as data, not instructions (spec §
|
|
86
|
+
/** Wraps a projection between explicit data markers so the model treats it as data, not instructions (spec §18). */
|
|
69
87
|
export function delimitProjection(artifactId: string, text: string): string {
|
|
70
88
|
return `<artifact-content id="${artifactId}">\n${text}\n</artifact-content>`;
|
|
71
89
|
}
|
|
@@ -110,14 +128,14 @@ function resolvePosition(ids: readonly string[], token: string): number | undefi
|
|
|
110
128
|
return undefined;
|
|
111
129
|
}
|
|
112
130
|
|
|
113
|
-
/** Applies mode/range/budget to a list of items.
|
|
131
|
+
/** Applies mode/range/budget to a list of items. Adapters with ordered items can use it directly. */
|
|
114
132
|
export function projectItems(
|
|
115
133
|
items: ProjectionItem[],
|
|
116
134
|
options: ProjectionOptions,
|
|
117
135
|
extras: { header?: string; footer?: string } = {},
|
|
118
136
|
): Projection {
|
|
119
137
|
let selected = items;
|
|
120
|
-
if (options.mode === 'range') {
|
|
138
|
+
if (options.mode === 'range' || options.mode === 'statements' || options.mode === 'section') {
|
|
121
139
|
const ids = selectRange(
|
|
122
140
|
items.map((item) => item.id),
|
|
123
141
|
options.range,
|