Skip to content

Commit 3ac5f81

Browse files
committed
docs(metadata): README and changeset describe the package-internal annotation path
Claude-Session: https://claude.ai/code/session_01TEhopqrWQYBycZzyJHpAZr Co-authored-by: Claude <noreply@anthropic.com>
1 parent 96e53e9 commit 3ac5f81

3 files changed

Lines changed: 6 additions & 4 deletions

File tree

‎.changeset/19852-typescript-serializer-per-type-annotation.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
If you type-check the `.ts` files that `MetadataManager.save()` / `FilesystemLoader.save()` write:
88

99
- An `object` file is byte-identical: `import type { ServiceObject } from '@objectstack/spec/data'` and `export const metadata: ServiceObject = …`.
10-
- Every other metadata type whose spec type is exactly the `z.input` type of its schema is now annotated with that type instead: `Flow` (`@objectstack/spec/automation`) for a `flow`, `Page` (`@objectstack/spec/ui`) for a `page`, `PermissionSet` (`@objectstack/spec/security`) for a `permission`, and so on, 28 metadata types in all. Such a file now fails `tsc` only where its content is not a valid item of its own type.
10+
- Every other metadata type whose spec type is exactly the `z.input` type of its schema is now annotated with that type instead: `Flow` (`@objectstack/spec/automation`) for a `flow`, `Page` (`@objectstack/spec/ui`) for a `page`, `PermissionSet` (`@objectstack/spec/security`) for a `permission`, and so on: 28 annotated metadata types, `object` included. Such a file now fails `tsc` only where its content is not a valid item of its own type.
1111
- `view`, `book`, `external_catalog` and any other metadata type (a plugin's own, for example) are written with no annotation and no import: `export const metadata = { … };`. `ViewMetadata` is `unknown` and `Book` is narrower than `BookSchema`, so neither would be a true annotation.
12-
- If you call `TypeScriptSerializer.serialize()` yourself, pass the item's metadata type as the new optional `SerializeOptions.metadataType` to get its annotation. Without it the file carries no annotation (it used to carry `ServiceObject`).
12+
- If you call `TypeScriptSerializer.serialize()` yourself, it now writes no annotation for any item, an object included. It used to write `ServiceObject` whatever the item was, and it cannot know the item's metadata type, so that annotation could be false. The loader picks the annotation through a package-internal function. The public API is unchanged: `SerializeOptions` and every declaration the package exports are as before.
1313

1414
Reading is unchanged: `deserialize` still reads the first JSON block, so a file written before this fix, `ServiceObject` annotation and all, still reads back. The `javascript` format is unchanged.

‎packages/metadata/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Serializers convert metadata objects to/from different file formats:
7474

7575
- **JSONSerializer** — `.json` files with optional key sorting
7676
- **YAMLSerializer** — `.yaml`/`.yml` files (JSON_SCHEMA for security)
77-
- **TypeScriptSerializer** — the `typescript` / `javascript` formats (`.ts` / `.js`): a JSON document wrapped in a module, the file format `FilesystemLoader` writes and reads for those two formats (`typescript` is `FilesystemLoader.save()`'s default, so `MetadataManager.save()` routed to the filesystem loader writes `{rootDir}/{type}/{name}.ts`). It writes `export const metadata = { …JSON… };` then `export default metadata;`. The `typescript` format also annotates that constant with the spec type of the item's metadata type, which `FilesystemLoader.save()` passes as `SerializeOptions.metadataType`, and imports it: `ServiceObject` from `@objectstack/spec/data` for an `object`, `Flow` from `@objectstack/spec/automation` for a `flow`, and so on, each exactly the `z.input` type of the schema `getMetadataTypeSchema()` resolves for that metadata type. A metadata type with no such spec type is written with no annotation and no import: `view` (its `ViewMetadata` type is `unknown`), `book`, `external_catalog`, a plugin's own type, and any `serialize()` call that passes no `metadataType`. It reads back the first `{ … }` block after the first `export const` (or, failing that, `export default`), which must be JSON: double-quoted keys and strings, no comments, no trailing commas, no functions. It is **not** an authoring shape: authored metadata such as a `*.object.ts` is written `ObjectSchema.create({ … })` (or `defineView()`, …), which this serializer never emits, and an authored file with unquoted keys is refused rather than read.
77+
- **TypeScriptSerializer** — the `typescript` / `javascript` formats (`.ts` / `.js`): a JSON document wrapped in a module, the file format `FilesystemLoader` writes and reads for those two formats (`typescript` is `FilesystemLoader.save()`'s default, so `MetadataManager.save()` routed to the filesystem loader writes `{rootDir}/{type}/{name}.ts`). It writes `export const metadata = { …JSON… };` then `export default metadata;`. A `typescript`-format file that `FilesystemLoader.save()` writes also annotates that constant with the spec type of the item's metadata type, and imports it: `ServiceObject` from `@objectstack/spec/data` for an `object`, `Flow` from `@objectstack/spec/automation` for a `flow`, and so on. Each is exactly the `z.input` type of the schema `getMetadataTypeSchema()` resolves for that metadata type. A metadata type with no such spec type is written with no annotation and no import: `view` (its `ViewMetadata` type is `unknown`), `book`, `external_catalog`, or a plugin's own type. Only the loader knows the metadata type, so it picks the annotation through a package-internal function, and `TypeScriptSerializer.serialize()` called directly writes no annotation. It reads back the first `{ … }` block after the first `export const` (or, failing that, `export default`), which must be JSON: double-quoted keys and strings, no comments, no trailing commas, no functions. It is **not** an authoring shape: authored metadata such as a `*.object.ts` is written `ObjectSchema.create({ … })` (or `defineView()`, …), which this serializer never emits, and an authored file with unquoted keys is refused rather than read.
7878

7979
### 4. Overlay / Customization System
8080

‎packages/metadata/src/serializers/typescript-serializer-annotation.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,15 @@ describe('TypeScriptSerializer annotation, per metadata type', () => {
124124
});
125125

126126
it('no exports entry of the package re-exports the internal channel', async () => {
127+
// Control: the name is spelled right, so the absences below can fail.
128+
expect(Object.keys(await import('./typescript-serializer.js'))).toContain('serializeTypeScriptForMetadataType');
127129
expect(EXPORT_ENTRY_SOURCES.length).toBe(5);
128130
for (const source of EXPORT_ENTRY_SOURCES) {
129131
const entry = (await import(source)) as Record<string, unknown>;
130132
expect(Object.keys(entry).length, source).toBeGreaterThan(0);
131133
expect(Object.keys(entry), source).not.toContain('serializeTypeScriptForMetadataType');
132134
}
133-
});
135+
}, 60_000);
134136

135137
it('round-trips every annotated body through serialize and deserialize', () => {
136138
for (const [metadataType, item] of Object.entries(REPRESENTATIVE)) {

0 commit comments

Comments
 (0)