@kubohiroya/turbowarp-structured-data 0.4.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.
@@ -0,0 +1,188 @@
1
+ import {renderJsonDocument, renderYamlDocument} from './codecs.js';
2
+ import type {JsonValue} from './core.js';
3
+ import {
4
+ NamedDataError,
5
+ type NamedDataBody,
6
+ type NamedDataMetadata,
7
+ type NamedDataProvider,
8
+ type NamedDataReference,
9
+ type NamedDataRepresentation,
10
+ type NamedDataResolveContext
11
+ } from '@kubohiroya/turbowarp-named-data/composition';
12
+
13
+ export const STRUCTURED_DATA_NAMESPACE = 'structured';
14
+ export const STRUCTURED_BODY_MAX_BYTES = 256 * 1024;
15
+
16
+ export interface StructuredDataBinding {
17
+ value: JsonValue;
18
+ revision: number;
19
+ nativeRepresentation: 'json' | 'yaml';
20
+ }
21
+
22
+ export interface StructuredDataBindingStore {
23
+ get(target: object, name: string): StructuredDataBinding | undefined;
24
+ }
25
+
26
+ export class StructuredDataNamedDataProvider implements NamedDataProvider {
27
+ public readonly namespace = STRUCTURED_DATA_NAMESPACE;
28
+ public readonly kind = 'structured' as const;
29
+ private readonly openBodies = new Set<symbol>();
30
+ private released = false;
31
+
32
+ public constructor(
33
+ private readonly store: StructuredDataBindingStore,
34
+ private readonly maxBodyBytes = STRUCTURED_BODY_MAX_BYTES
35
+ ) {}
36
+
37
+ public canResolve(
38
+ reference: NamedDataReference,
39
+ representation: NamedDataRepresentation
40
+ ): boolean {
41
+ return (
42
+ reference.namespace === this.namespace &&
43
+ reference.kind === this.kind &&
44
+ reference.scope === 'target' &&
45
+ (representation === 'json' || representation === 'yaml')
46
+ );
47
+ }
48
+
49
+ public stat(
50
+ reference: NamedDataReference,
51
+ representation: NamedDataRepresentation,
52
+ context: NamedDataResolveContext
53
+ ): NamedDataMetadata {
54
+ const resolved = this.resolve(reference, representation, context);
55
+ return this.metadata(
56
+ reference,
57
+ resolved.nativeRepresentation,
58
+ representation,
59
+ resolved.bytes.byteLength,
60
+ resolved.revision
61
+ );
62
+ }
63
+
64
+ public openBody(
65
+ reference: NamedDataReference,
66
+ representation: NamedDataRepresentation,
67
+ context: NamedDataResolveContext
68
+ ): NamedDataBody {
69
+ const resolved = this.resolve(reference, representation, context);
70
+ const handle = Symbol('structured-body');
71
+ this.openBodies.add(handle);
72
+ let released = false;
73
+ return {
74
+ ...this.metadata(
75
+ reference,
76
+ resolved.nativeRepresentation,
77
+ representation,
78
+ resolved.bytes.byteLength,
79
+ resolved.revision
80
+ ),
81
+ body: resolved.bytes,
82
+ release: () => {
83
+ if (released) return;
84
+ released = true;
85
+ this.openBodies.delete(handle);
86
+ }
87
+ };
88
+ }
89
+
90
+ public release(): void {
91
+ this.released = true;
92
+ this.openBodies.clear();
93
+ }
94
+
95
+ public clearSession(): void {
96
+ this.openBodies.clear();
97
+ }
98
+
99
+ private resolve(
100
+ reference: NamedDataReference,
101
+ representation: NamedDataRepresentation,
102
+ context: NamedDataResolveContext
103
+ ): {bytes: Uint8Array; revision: number; nativeRepresentation: 'json' | 'yaml'} {
104
+ if (this.released) {
105
+ throw new NamedDataError('NAMED_DATA_PROVIDER_RELEASED', 'The structured provider was released.');
106
+ }
107
+ this.validateReference(reference, context);
108
+ this.throwIfAborted(context.signal);
109
+ const binding = this.store.get(context.target as object, reference.name);
110
+ if (!binding) {
111
+ throw new NamedDataError(
112
+ 'NAMED_DATA_NOT_FOUND',
113
+ `Structured data does not exist: ${reference.name}`
114
+ );
115
+ }
116
+ const text = this.render(binding.value, representation);
117
+ const bytes = new TextEncoder().encode(text);
118
+ if (bytes.byteLength > this.maxBodyBytes) {
119
+ throw new NamedDataError(
120
+ 'NAMED_DATA_BODY_TOO_LARGE',
121
+ `Encoded body is ${bytes.byteLength} bytes; maximum is ${this.maxBodyBytes}.`
122
+ );
123
+ }
124
+ this.throwIfAborted(context.signal);
125
+ return {
126
+ bytes,
127
+ revision: binding.revision,
128
+ nativeRepresentation: binding.nativeRepresentation
129
+ };
130
+ }
131
+
132
+ private validateReference(
133
+ reference: NamedDataReference,
134
+ context: NamedDataResolveContext
135
+ ): void {
136
+ if (reference.namespace !== this.namespace || reference.name.trim().length === 0) {
137
+ throw new NamedDataError('NAMED_DATA_INVALID_REF', 'Invalid structured data reference.');
138
+ }
139
+ if (reference.kind !== this.kind) {
140
+ throw new NamedDataError(
141
+ 'NAMED_DATA_KIND_MISMATCH',
142
+ `Expected ${this.kind}, received ${reference.kind}.`
143
+ );
144
+ }
145
+ if (reference.scope !== 'target' || !context.target) {
146
+ throw new NamedDataError(
147
+ 'NAMED_DATA_SCOPE_MISMATCH',
148
+ 'Structured data currently requires a target scope and target context.'
149
+ );
150
+ }
151
+ }
152
+
153
+ private render(value: JsonValue, representation: NamedDataRepresentation): string {
154
+ if (representation === 'json') return renderJsonDocument(value);
155
+ if (representation === 'yaml') return renderYamlDocument(value);
156
+ throw new NamedDataError(
157
+ 'NAMED_DATA_REPRESENTATION_UNSUPPORTED',
158
+ `Structured data cannot be rendered as ${representation}.`
159
+ );
160
+ }
161
+
162
+ private metadata(
163
+ reference: NamedDataReference,
164
+ nativeRepresentation: 'json' | 'yaml',
165
+ representation: NamedDataRepresentation,
166
+ byteLength: number,
167
+ revision: number
168
+ ): NamedDataMetadata {
169
+ return {
170
+ reference: {...reference},
171
+ nativeRepresentation,
172
+ representation,
173
+ mediaType:
174
+ representation === 'json'
175
+ ? 'application/json; charset=utf-8'
176
+ : 'application/yaml; charset=utf-8',
177
+ byteLength,
178
+ revision: String(revision),
179
+ replayable: true
180
+ };
181
+ }
182
+
183
+ private throwIfAborted(signal: AbortSignal | undefined): void {
184
+ if (signal?.aborted) {
185
+ throw new NamedDataError('NAMED_DATA_ABORTED', 'Body resolution was aborted.');
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,183 @@
1
+ # TurboWarp-<Extension-Name>
2
+
3
+ [English](README.md) | [日本語](README.ja.md)
4
+
5
+ <何をするかを一文で説明>ためのTurboWarp機能拡張です。
6
+
7
+ **利用ガイド:** [English](<PAGES_URL>/)
8
+
9
+ ## できること
10
+
11
+ - <利用者ができること1>
12
+ - <利用者ができること2>
13
+ - <利用者ができること3>
14
+
15
+ 実装ファイルの説明ではなく、利用者から見た結果を書きます。
16
+
17
+ ## ドキュメントとブロックアイコン
18
+
19
+ この機能拡張は、英語版の利用者向けドキュメントをGitHub Pagesで公開します。
20
+
21
+ - `docsURI` はGitHub Pages上の英語版ドキュメント `<PAGES_URL>/` を指します。
22
+ - `blockIconURI` は `data:image/svg+xml;base64,...` 形式の自己完結したSVGとします。
23
+ - ブロックアイコンは機能拡張の内容を直感的に表す、単純で識別しやすい図柄にします。公開版では汎用の仮アイコンをそのまま使用しません。
24
+
25
+ ## 動作条件と安全上の注意
26
+
27
+ - <TurboWarp Desktop/Web/Packagerの対応>
28
+ - <Browser APIまたはcompanion extension>
29
+ - <sandbox条件>
30
+
31
+ > [!IMPORTANT]
32
+ > この機能拡張は<理由>のため、サンドボックスなしで実行する必要があります。
33
+ > 信頼できる配布元の機能拡張だけを読み込んでください。
34
+
35
+ sandbox互換の場合は上の注意書きを削除します。
36
+
37
+ ## インストール
38
+
39
+ ### 完成済みJavaScript
40
+
41
+ 1. [`dist/<file>.js`](dist/<file>.js?raw=1)をダウンロードします。
42
+ 2. TurboWarpで**機能拡張**を開きます。
43
+ 3. **カスタム機能拡張**からfileを読み込みます。
44
+ 4. 必要な場合は**サンドボックスなしで実行する**を許可します。
45
+
46
+ 完成済みの検証対象JavaScriptをrepositoryへcommitしているため、利用者が
47
+ Node.jsやbuild環境を用意する必要はありません。
48
+
49
+ ### npm package
50
+
51
+ 検証済みのversionをexact pinします。
52
+
53
+ ```bash
54
+ pnpm add --save-exact @kubohiroya/<package>@<version>
55
+ ```
56
+
57
+ standalone bundle:
58
+
59
+ ```text
60
+ node_modules/@kubohiroya/<package>/dist/<file>.js
61
+ ```
62
+
63
+ version固定CDN URL:
64
+
65
+ ```text
66
+ https://cdn.jsdelivr.net/npm/@kubohiroya/<package>@<version>/dist/<file>.js
67
+ ```
68
+
69
+ `<version>`を手作業で保守する場合は、`package.json`との一致をCIで検査します。
70
+
71
+ ## クイックスタート
72
+
73
+ 1. <手順1>
74
+ 2. <手順2>
75
+ 3. <手順3>
76
+
77
+ ```text
78
+ <最小のTurboWarp block flow>
79
+ ```
80
+
81
+ ## ブロックリファレンス
82
+
83
+ block referenceは
84
+ [`src/block-definitions.json`](src/block-definitions.json)から生成します。
85
+ 生成範囲を直接編集しないでください。
86
+
87
+ <!-- BEGIN GENERATED BLOCKS -->
88
+
89
+ <自動生成block文書>
90
+
91
+ <!-- END GENERATED BLOCKS -->
92
+
93
+ ## 重要な動作
94
+
95
+ | 状況 | 動作 |
96
+ |---|---|
97
+ | <case> | <正確な動作> |
98
+ | project停止 | <cleanup> |
99
+ | project再読込 | <cleanupまたは復元> |
100
+ | 無効な入力 | <errorまたはfallback> |
101
+
102
+ ## Composition API
103
+
104
+ Composition APIがないpackageでは、このsectionを削除します。
105
+
106
+ Composition APIをimportしても、standalone TurboWarp機能拡張は自動登録されません。
107
+
108
+ ```ts
109
+ import {create<Feature>Composition} from '@kubohiroya/<package>/composition';
110
+
111
+ const feature = create<Feature>Composition({
112
+ // 明示的な依存
113
+ });
114
+
115
+ // capabilityを利用する。
116
+
117
+ feature.releaseAll();
118
+ ```
119
+
120
+ 所有、取消し、`AbortSignal`、project lifecycle、副作用、browser/network/storage/
121
+ renderer境界、`release()`/`releaseAll()`を説明します。
122
+
123
+ 完全なmethod referenceは
124
+ [`docs/composition-api.md`](docs/composition-api.md)へ分けます。
125
+
126
+ ## 連携
127
+
128
+ | 利用側 | 関係 |
129
+ |---|---|
130
+ | `<consumer package>` | <required/optional/capability injection> |
131
+
132
+ 依存方向はconsumerからproviderとし、private fieldを公開連携方法として書きません。
133
+
134
+ ## 互換性
135
+
136
+ | 識別子 | 値 | 安定性 |
137
+ |---|---|---|
138
+ | 製品名 | `TurboWarp-<Extension-Name>` | 利用者向け表示 |
139
+ | repository | `kubohiroya/<repository>` | current source |
140
+ | npm package | `@kubohiroya/<package>` | public package contract |
141
+ | extension ID | `<extensionid>` | SB3に保存。変更にはmigrationが必要 |
142
+
143
+ 複数語からなるTurboWarp機能拡張の正式表示名では、すべての構成語をハイフンで連結します。例: `TurboWarp-Asset-Manager`、`TurboWarp-Runtime-Expression`、`TurboWarp-Diagnostic-Overlay`。
144
+
145
+ extension IDまたはopcode変更時はschema-aware migrationを説明し、保存済み識別子が変わった場合にJavaScriptだけの差替えを案内しません。
146
+
147
+ ## 開発
148
+
149
+ `engines`のNode.jsと、`packageManager`に宣言したpnpmを使用します。
150
+
151
+ ```bash
152
+ corepack enable
153
+ pnpm install --frozen-lockfile
154
+ pnpm check
155
+ ```
156
+
157
+ | command | 用途 |
158
+ |---|---|
159
+ | `pnpm dev` | source変更中の継続build |
160
+ | `pnpm test` | test |
161
+ | `pnpm docs` | block文書生成 |
162
+ | `pnpm check:dist` | commit済みbuild artifactの検証 |
163
+ | `pnpm pack:check` | npm package内容の確認 |
164
+ | `pnpm release:check` | release dry run |
165
+
166
+ ## リリース
167
+
168
+ 1. `CHANGELOG.md`がある場合は更新する。
169
+ 2. `package.json`のversionを更新する。
170
+ 3. `pnpm check`とrepository固有のrelease checkを実行する。
171
+ 4. release PRをmergeする。
172
+ 5. 検証済みcommitへ`v<version>`tagを付ける。
173
+ 6. 必要に応じて同じversionをnpmへpublishする。
174
+ 7. GitHub Release、npm tarball、GitHub Pages、`docsURI`、block icon、CDNを確認する。
175
+
176
+ release consistency checkでは、exact-version README/CDN例に加え、`docsURI`が英語版Pagesを指すこと、`blockIconURI`が`data:image/svg+xml;base64,...`形式であることを検証します。
177
+
178
+ ## ライセンス
179
+
180
+ [Mozilla Public License 2.0](LICENSE)(SPDX: `MPL-2.0`)で提供します。
181
+
182
+ third-party componentを含む場合は
183
+ [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)に記載します。
@@ -0,0 +1,198 @@
1
+ # TurboWarp-<Extension-Name>
2
+
3
+ [English](README.md) | [日本語](README.ja.md)
4
+
5
+ A TurboWarp extension that <one-sentence purpose>.
6
+
7
+ **User guide:** [English](<PAGES_URL>/)
8
+
9
+ ## What it does
10
+
11
+ - <User-visible capability 1>
12
+ - <User-visible capability 2>
13
+ - <User-visible capability 3>
14
+
15
+ Keep this section about outcomes, not implementation files.
16
+
17
+ ## Documentation and block icon
18
+
19
+ This extension publishes its English user documentation with GitHub Pages.
20
+
21
+ - `docsURI` points to the English GitHub Pages documentation: `<PAGES_URL>/`.
22
+ - `blockIconURI` is a self-contained SVG encoded as `data:image/svg+xml;base64,...`.
23
+ - The block icon should be a simple, recognizable symbol that represents this extension and improves block readability. Do not use a generic placeholder icon in a released extension.
24
+
25
+ ## Requirements and safety
26
+
27
+ - <TurboWarp Desktop / Web / Packager support>
28
+ - <Browser API or companion extension requirements>
29
+ - <Sandbox requirement>
30
+
31
+ > [!IMPORTANT]
32
+ > This extension must run without the sandbox because <reason>. Load unsandboxed
33
+ > extension code only from a source you trust.
34
+
35
+ Delete the notice above when the extension is sandbox-compatible.
36
+
37
+ ## Installation
38
+
39
+ ### Ready-to-use JavaScript
40
+
41
+ 1. Download [`dist/<file>.js`](dist/<file>.js?raw=1).
42
+ 2. Open **Extensions** in TurboWarp.
43
+ 3. Choose **Custom Extension** and load the file.
44
+ 4. Enable **Run extension without sandbox** when required.
45
+
46
+ The reviewed JavaScript build is committed to this repository, so users do not
47
+ need Node.js to install the extension.
48
+
49
+ ### npm package
50
+
51
+ Install an exact version that you have reviewed:
52
+
53
+ ```bash
54
+ pnpm add --save-exact @kubohiroya/<package>@<version>
55
+ ```
56
+
57
+ Load the standalone bundle from:
58
+
59
+ ```text
60
+ node_modules/@kubohiroya/<package>/dist/<file>.js
61
+ ```
62
+
63
+ A version-pinned CDN URL is:
64
+
65
+ ```text
66
+ https://cdn.jsdelivr.net/npm/@kubohiroya/<package>@<version>/dist/<file>.js
67
+ ```
68
+
69
+ Do not maintain `<version>` manually unless CI checks it against `package.json`.
70
+
71
+ ## Quick start
72
+
73
+ 1. <Step 1>
74
+ 2. <Step 2>
75
+ 3. <Step 3>
76
+
77
+ ```text
78
+ <Minimal TurboWarp block flow>
79
+ ```
80
+
81
+ ## Block reference
82
+
83
+ The block reference is generated from
84
+ [`src/block-definitions.json`](src/block-definitions.json). Do not edit the
85
+ generated section manually.
86
+
87
+ <!-- BEGIN GENERATED BLOCKS -->
88
+
89
+ <Generated block documentation>
90
+
91
+ <!-- END GENERATED BLOCKS -->
92
+
93
+ ## Important behavior
94
+
95
+ | Situation | Behavior |
96
+ |---|---|
97
+ | <Case> | <Exact behavior> |
98
+ | Project stop | <Cleanup behavior> |
99
+ | Project reload | <Cleanup or restoration behavior> |
100
+ | Invalid input | <Error or fallback behavior> |
101
+
102
+ ## Composition API
103
+
104
+ Delete this section when the package has no Composition API.
105
+
106
+ Importing the Composition API does not register the standalone TurboWarp
107
+ extension.
108
+
109
+ ```ts
110
+ import {create<Feature>Composition} from '@kubohiroya/<package>/composition';
111
+
112
+ const feature = create<Feature>Composition({
113
+ // Explicit dependencies
114
+ });
115
+
116
+ // Use the capability.
117
+
118
+ feature.releaseAll();
119
+ ```
120
+
121
+ Explain:
122
+
123
+ - ownership;
124
+ - cancellation and `AbortSignal`;
125
+ - target/project lifecycle;
126
+ - side effects;
127
+ - browser, network, storage, and renderer boundaries;
128
+ - `release()` / `releaseAll()` behavior.
129
+
130
+ Move the complete method reference to [`docs/composition-api.md`](docs/composition-api.md).
131
+
132
+ ## Integration
133
+
134
+ | Consumer | Relationship |
135
+ |---|---|
136
+ | `<consumer package>` | <required / optional / capability injection> |
137
+
138
+ Dependencies must point from the consumer to the provider. Do not document
139
+ private-field access as an integration API.
140
+
141
+ ## Compatibility
142
+
143
+ | Identifier | Value | Stability |
144
+ |---|---|---|
145
+ | Product name | `TurboWarp-<Extension-Name>` | Human-facing |
146
+ | Repository | `kubohiroya/<repository>` | Current source location |
147
+ | npm package | `@kubohiroya/<package>` | Public package contract |
148
+ | Extension ID | `<extensionid>` | Stored in SB3; migration required to change |
149
+
150
+ Use hyphens between every component of a multi-word TurboWarp extension name,
151
+ for example `TurboWarp-Asset-Manager`, `TurboWarp-Runtime-Expression`, and
152
+ `TurboWarp-Diagnostic-Overlay`.
153
+
154
+ Document schema-aware migrations when an extension ID or opcode changes. Never
155
+ instruct users to replace JavaScript alone when stored identifiers have changed.
156
+
157
+ ## Development
158
+
159
+ Use Node.js `<minimum version>` and the pnpm version declared by
160
+ `packageManager`.
161
+
162
+ ```bash
163
+ corepack enable
164
+ pnpm install --frozen-lockfile
165
+ pnpm check
166
+ ```
167
+
168
+ Useful commands:
169
+
170
+ | Command | Purpose |
171
+ |---|---|
172
+ | `pnpm dev` | Rebuild while source files change |
173
+ | `pnpm test` | Run tests |
174
+ | `pnpm docs` | Regenerate block documentation |
175
+ | `pnpm check:dist` | Verify committed build artifacts |
176
+ | `pnpm pack:check` | Inspect the npm package |
177
+ | `pnpm release:check` | Dry-run the release |
178
+
179
+ ## Release
180
+
181
+ 1. Update `CHANGELOG.md` when present.
182
+ 2. Update the version in `package.json`.
183
+ 3. Run `pnpm check` and the repository release checks.
184
+ 4. Merge the release PR.
185
+ 5. Create tag `v<version>` on the verified commit.
186
+ 6. Publish the same version to npm when applicable.
187
+ 7. Verify GitHub Release, npm tarball, GitHub Pages, `docsURI`, block icon, and CDN artifacts.
188
+
189
+ The release consistency check should verify every exact-version README and CDN
190
+ example, and should ensure that `docsURI` still resolves to the English Pages
191
+ site and `blockIconURI` remains a `data:image/svg+xml;base64,...` URI.
192
+
193
+ ## License
194
+
195
+ [Mozilla Public License 2.0](LICENSE) (SPDX: `MPL-2.0`).
196
+
197
+ Third-party components, when present, are listed in
198
+ [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).