@classytic/arc 2.20.0 → 2.22.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/{BaseController-D-MHZ27_.mjs → BaseController-C6FlRWxF.mjs} +2 -2
- package/dist/{EventTransport-IEn7Tn-Q.d.mts → EventTransport-QmY7A258.d.mts} +0 -1
- package/dist/{QueryCache-TRfXau3W.d.mts → QueryCache-Z1B38koh.d.mts} +0 -1
- package/dist/{actionPermissions-C9RMiIpc.mjs → actionPermissions-ze70xXJi.mjs} +1 -1
- package/dist/audit/index.d.mts +9 -5
- package/dist/auth/audit.d.mts +7 -4
- package/dist/auth/audit.mjs +5 -0
- package/dist/auth/index.d.mts +35 -14
- package/dist/auth/index.mjs +6 -3
- package/dist/auth/redis-session.d.mts +1 -2
- package/dist/cache/index.d.mts +2 -3
- package/dist/cli/commands/describe.d.mts +1 -2
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/context/index.d.mts +2 -2
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-CWhyGkiM.mjs → core-b6fgFgnd.mjs} +129 -17
- package/dist/{createActionRouter-HlQso0jO.mjs → createActionRouter-h1cEEaEX.mjs} +10 -6
- package/dist/{createAggregationRouter-5LzPzgw1.mjs → createAggregationRouter-B1C0kvTL.mjs} +3 -3
- package/dist/{createApp-D6uwzCKu.mjs → createApp-Ci7yG5gv.mjs} +390 -192
- package/dist/discovery/index.d.mts +0 -1
- package/dist/docs/index.d.mts +1 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/{elevation-xGUBq6p_.d.mts → elevation-C9s5hFP_.d.mts} +0 -1
- package/dist/encryption/index.d.mts +2 -144
- package/dist/encryption/index.mjs +9 -5
- package/dist/{errorHandler-Dxf6P_kV.mjs → errorHandler-BTzXSwIg.mjs} +24 -3
- package/dist/{errorHandler-BtUUDPEa.d.mts → errorHandler-C6yv4ZTW.d.mts} +13 -1
- package/dist/{errors-DOHkjQlD.d.mts → errors-DHcHVAgk.d.mts} +0 -1
- package/dist/{eventPlugin-DbBFd3x_.d.mts → eventPlugin-A6EbthXR.d.mts} +10 -6
- package/dist/events/index.d.mts +9 -6
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -2
- package/dist/factory/index.d.mts +30 -9
- package/dist/factory/index.mjs +35 -4
- package/dist/{fastify-augmentation-CCQJEDgh.d.mts → fastify-augmentation-9_fuetB4.d.mts} +2 -3
- package/dist/fields-D3lhgzhD.d.mts +121 -0
- package/dist/health-ScyjIVo5.mjs +202 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +4 -3
- package/dist/idempotency/index.mjs +2 -0
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{types-BHMKlMBU.d.mts → index-BEu4wjbL.d.mts} +241 -80
- package/dist/{index-DUa8WHz3.d.mts → index-D8MKYGDa.d.mts} +88 -7
- package/dist/{index-_--GyzcO.d.mts → index-DbtgCOCj.d.mts} +1324 -1228
- package/dist/{index-CdFkIMEd.d.mts → index-ogIHvOjo.d.mts} +36 -7
- package/dist/index.d.mts +8 -7
- package/dist/index.mjs +6 -6
- package/dist/{init-YH_nslQR.mjs → init-DleK3p9f.mjs} +272 -139
- package/dist/integrations/event-gateway.d.mts +2 -3
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/jobs.d.mts +0 -1
- package/dist/integrations/mcp/index.d.mts +6 -7
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -2
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +2 -2
- package/dist/integrations/webhooks.d.mts +8 -5
- package/dist/integrations/webhooks.mjs +9 -5
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -2
- package/dist/integrations/websocket-redis.d.mts +1 -2
- package/dist/integrations/websocket.d.mts +2 -2
- package/dist/jsonBody-BlzAoGCe.mjs +43 -0
- package/dist/{loadResources-B8UmpITt.mjs → loadResources-DpU8pfjV.mjs} +4 -3
- package/dist/logger/index.d.mts +26 -1
- package/dist/logger/index.mjs +39 -1
- package/dist/{mcpPlugin-CuW6L_uD.mjs → mcpPlugin-ByA2tf6D.mjs} +8 -4
- package/dist/middleware/index.d.mts +1 -2
- package/dist/{module-JYH9V0I8.d.mts → module-B4u10iOJ.d.mts} +71 -1
- package/dist/{openapi-Dq6RbEGK.mjs → openapi-BRpMZD7C.mjs} +3 -3
- package/dist/permissions/index.d.mts +4 -3
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-CgbOLJZG.mjs → permissions-BJMuKMl7.mjs} +33 -1
- package/dist/pipeline/index.d.mts +1 -2
- package/dist/plugins/index.d.mts +96 -7
- package/dist/plugins/index.mjs +86 -268
- package/dist/plugins/response-cache.d.mts +5 -3
- package/dist/plugins/response-cache.mjs +1 -0
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +7 -4
- package/dist/presets/filesUpload.d.mts +4 -5
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +1 -2
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -2
- package/dist/presets/search.d.mts +2 -3
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-DWsYQdbV.mjs → presets-gw0ciIUD.mjs} +1 -1
- package/dist/{pushref-registry-DnCqL3aa.d.mts → pushref-registry-BbcnpNTm.d.mts} +4 -2
- package/dist/{queryCachePlugin-xzFyKAjT.d.mts → queryCachePlugin-CfYWq-Ak.d.mts} +1 -2
- package/dist/rateLimitKey-BoWRH3e7.mjs +14 -0
- package/dist/{redis-C3W0qbz8.d.mts → redis-UDtLaNj6.d.mts} +0 -1
- package/dist/{redis-stream-DsP1vqZB.d.mts → redis-stream-iVR2l7aS.d.mts} +1 -2
- package/dist/{registerRoute-DGSREwPw.mjs → registerRoute-xHc42fwB.mjs} +38 -18
- package/dist/registry/index.d.mts +2 -2
- package/dist/requestId-5BhTVzBv.mjs +113 -0
- package/dist/schemaConverter-Ds5reSGs.mjs +168 -0
- package/dist/{schemaIR-ajetw3Fv.mjs → schemaIR-B-6SPe7g.mjs} +2 -2
- package/dist/schemas/index.d.mts +0 -1
- package/dist/scim/index.d.mts +0 -1
- package/dist/scim/index.mjs +6 -7
- package/dist/scope/index.d.mts +2 -3
- package/dist/scope/index.mjs +2 -76
- package/dist/scope-k15RXfcq.mjs +64 -0
- package/dist/{sessionManager-BXG-HGwG.d.mts → sessionManager-BHOM3N_0.d.mts} +10 -6
- package/dist/{storage-Co_PQpeQ.d.mts → storage-DLUZ04kK.d.mts} +8 -4
- package/dist/testing/index.d.mts +83 -4
- package/dist/testing/index.mjs +74 -4
- package/dist/testing/storageContract.d.mts +1 -2
- package/dist/{tracing-D8jlGXP7.d.mts → tracing-CsGKQJGg.d.mts} +0 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-C9b2HR5W.d.mts → types-Bc42CUyk.d.mts} +1 -2
- package/dist/types-Cc91nXuA.d.mts +132 -0
- package/dist/{fields-C7ahdGYf.d.mts → types-Csxga7d3.d.mts} +2 -123
- package/dist/{types-LNebCoJx.d.mts → types-DH0GupI7.d.mts} +8 -4
- package/dist/usage/index.d.mts +142 -0
- package/dist/usage/index.mjs +0 -0
- package/dist/utils/index.d.mts +220 -144
- package/dist/utils/index.mjs +3 -3
- package/dist/{utils-ChjYQpjn.mjs → utils-FhJ88TJE.mjs} +129 -39
- package/dist/{versioning-D_eBCfNr.d.mts → versioning-DLwklPlG.d.mts} +1 -7
- package/dist/{websocket-MlYlrYq0.d.mts → websocket-uuWqM6UY.d.mts} +5 -4
- package/llms.txt +39 -0
- package/package.json +453 -447
- package/skills/arc/SKILL.md +14 -8
- package/skills/arc/references/api-reference.md +34 -0
- package/skills/arc/references/auth.md +21 -4
- package/skills/arc/references/mcp.md +1 -1
- package/skills/arc/references/production.md +44 -2
- package/skills/arc-code-review/SKILL.md +1 -1
- package/skills/arc-code-review/references/anti-patterns.md +71 -18
- package/skills/arc-code-review/references/arc-cheatsheet.md +15 -2
- package/skills/arc-code-review/references/migration-recipes.md +7 -7
- package/skills/arc-code-review/references/mongokit-migration.md +4 -3
- package/skills/arc-module-publishing/SKILL.md +243 -0
- package/skills/arc-module-publishing/references/authoring-modules.md +327 -0
- package/skills/arc-module-publishing/references/package-manifest.md +157 -0
- package/skills/arc-module-publishing/references/publishing-workflow.md +182 -0
- package/dist/preloadResources-CHw3_oA_.mjs +0 -45
- package/dist/schemaConverter-U0MnO9vd.mjs +0 -110
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# package.json + tsdown reference
|
|
2
|
+
|
|
3
|
+
Copy-paste manifest for an arc ecosystem package, plus the dependency-field rules and the
|
|
4
|
+
optional-peer typing pattern. Values below are the real shapes shipped by
|
|
5
|
+
`@classytic/arc-approval` — adapt names, keep the structure.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## package.json
|
|
10
|
+
|
|
11
|
+
```jsonc
|
|
12
|
+
{
|
|
13
|
+
"name": "@classytic/arc-<domain>",
|
|
14
|
+
"version": "0.1.0",
|
|
15
|
+
"description": "<one line — what this contributes to an arc host>",
|
|
16
|
+
"type": "module",
|
|
17
|
+
|
|
18
|
+
// Public entry. Add subpaths ("./adapter", "./testing") the same way if you split surfaces.
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.mts",
|
|
22
|
+
"default": "./dist/index.mjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.mjs",
|
|
26
|
+
"types": "./dist/index.d.mts",
|
|
27
|
+
|
|
28
|
+
"sideEffects": false, // pure ESM, tree-shakable — no import-for-effect modules
|
|
29
|
+
"files": ["dist"], // ship only build output; never src/, tests/, configs
|
|
30
|
+
"publishConfig": { "access": "public" },
|
|
31
|
+
"engines": { "node": ">=22" },
|
|
32
|
+
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsdown",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"lint": "biome check src/",
|
|
37
|
+
"prepublishOnly": "npm run typecheck && npm run lint && npm run test && npm run build"
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// CONTRACTS — what the host must provide. `>=`, never `^`.
|
|
41
|
+
// Floor >=2.21.0 if you use ResourceSeams / mergeResourceConfig / permissionMatrix /
|
|
42
|
+
// schedulesPlugin / errorMappers — declare the floor that matches the APIs you import.
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@classytic/arc": ">=2.21.0",
|
|
45
|
+
"@classytic/primitives": ">=0.9.0"
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Optional peers: declared here AND marked optional below. Host installs only if used.
|
|
49
|
+
// "peerDependencies": { ..., "zod": ">=3.23.0", "ioredis": ">=5.0.0" },
|
|
50
|
+
// "peerDependenciesMeta": {
|
|
51
|
+
// "zod": { "optional": true },
|
|
52
|
+
// "ioredis": { "optional": true }
|
|
53
|
+
// },
|
|
54
|
+
|
|
55
|
+
// TEST versions — pin with ^, floor >= each peer floor. Not shipped.
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@classytic/arc": "^2.21.0",
|
|
58
|
+
"@classytic/primitives": "^0.9.1",
|
|
59
|
+
"@biomejs/biome": "^2.5.2",
|
|
60
|
+
"@types/node": "^22.19.21",
|
|
61
|
+
"tsdown": "^0.22.5",
|
|
62
|
+
"typescript": "^7.0.2",
|
|
63
|
+
"vitest": "^4.1.9"
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
"keywords": ["arc", "fastify", "<domain>", "classytic"],
|
|
67
|
+
"license": "MIT",
|
|
68
|
+
"author": "Classytic",
|
|
69
|
+
"repository": { "type": "git", "url": "https://github.com/classytic/arc-ecosystem.git", "directory": "packages/arc-<domain>" }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
In an **npm workspace**, devDeps common to all packages hoist to the root `package.json`; a
|
|
74
|
+
package only lists devDeps unique to it. The peer-skew gate reads root + package devDeps
|
|
75
|
+
together (see publishing-workflow.md).
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Dependency-field cheat sheet
|
|
80
|
+
|
|
81
|
+
| Field | What goes here | Range style | Why |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| `dependencies` | (usually nothing) truly-private bundled helpers only | `^` | Ships with you; host can't override. Almost never needed. |
|
|
84
|
+
| `peerDependencies` | arc, fastify, primitives, kits, zod, ioredis | **`>=`** | A contract/floor. Host resolves the real version; don't block their upgrades. |
|
|
85
|
+
| `peerDependenciesMeta` | mark optional peers `{ optional: true }` | — | Host installs only if they use that path. |
|
|
86
|
+
| `devDependencies` | concrete versions your tests run against | `^` | The version you actually exercise; floor must be `>=` peer floor. |
|
|
87
|
+
|
|
88
|
+
**The trap:** putting `@classytic/arc` or `fastify` in `dependencies`, or writing a peer as
|
|
89
|
+
`^2.20.0`. Both force the host into *your* version choice. A published arc package makes as
|
|
90
|
+
few decisions for the host as possible — it states floors and gets out of the way.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## tsdown.config.ts
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { defineConfig } from "tsdown";
|
|
98
|
+
|
|
99
|
+
export default defineConfig({
|
|
100
|
+
entry: ["src/index.ts"], // add entries for extra subpaths: ["src/index.ts", "src/adapter.ts"]
|
|
101
|
+
format: ["esm"], // ESM only — arc is ESM only
|
|
102
|
+
dts: true, // emit .d.mts
|
|
103
|
+
sourcemap: false,
|
|
104
|
+
clean: true,
|
|
105
|
+
treeshake: true,
|
|
106
|
+
target: "node22",
|
|
107
|
+
outDir: "dist",
|
|
108
|
+
deps: {
|
|
109
|
+
// Bundle NOTHING — same never-bundle contract as arc's own tsdown config.
|
|
110
|
+
skipNodeModulesBundle: true,
|
|
111
|
+
neverBundle: [/^@classytic\//],
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Why bundle nothing: if you inline `@classytic/arc` (or primitives, or a kit), the host ends up
|
|
117
|
+
with two copies — yours frozen in `dist/`, theirs in `node_modules`. Arc's plugin registry,
|
|
118
|
+
event transport, and scope decorators are identity-sensitive; two copies register twice and
|
|
119
|
+
diverge. `neverBundle: [/^@classytic\//]` guarantees the host's single installed copy is the
|
|
120
|
+
only one at runtime.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Optional peers: keep their types out of your public surface
|
|
125
|
+
|
|
126
|
+
If a peer is optional (a host might not install it), its types **must not** appear in your
|
|
127
|
+
published `.d.mts` — otherwise a host without it gets a compile error importing your package.
|
|
128
|
+
Use structural typing: describe the shape you need with a local interface, don't import the
|
|
129
|
+
dependency's types into the public boundary.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// ❌ leaks zod into dist/index.d.mts — breaks hosts that didn't install zod
|
|
133
|
+
import type { ZodType } from "zod";
|
|
134
|
+
export function withValidation(schema: ZodType) { /* ... */ }
|
|
135
|
+
|
|
136
|
+
// ✅ structural: the public signature owes nothing to zod's package
|
|
137
|
+
export interface StandardSchemaLike<T> {
|
|
138
|
+
readonly "~standard": { validate: (v: unknown) => { value: T } | { issues: unknown[] } };
|
|
139
|
+
}
|
|
140
|
+
export function withValidation<T>(schema: StandardSchemaLike<T>) { /* ... */ }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For **shipped action/route schemas, use JSON Schema**, not zod objects. Arc converts JSON
|
|
144
|
+
Schema natively; zod stays an *optional* convenience peer for hosts that prefer it. A package
|
|
145
|
+
that hard-requires zod for its schemas has made zod a non-optional dependency by the back door.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
schema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
required: ["stepId", "decision"],
|
|
151
|
+
properties: {
|
|
152
|
+
stepId: { type: "string", minLength: 1 },
|
|
153
|
+
decision: { type: "string", enum: ["approved", "rejected"] },
|
|
154
|
+
},
|
|
155
|
+
additionalProperties: false,
|
|
156
|
+
}
|
|
157
|
+
```
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Publishing workflow
|
|
2
|
+
|
|
3
|
+
Monorepo layout, the release gates, and the classytic bot-identity publish steps.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Monorepo layout (npm workspaces)
|
|
8
|
+
|
|
9
|
+
Prefer **one workspace holding many small packages** over one repo per package — they release
|
|
10
|
+
together and share tooling. The `@classytic/arc-ecosystem` layout:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
arc-ecosystem/
|
|
14
|
+
package.json # workspaces: ["packages/*"], shared devDeps, verify script
|
|
15
|
+
scripts/
|
|
16
|
+
check-peer-skew.mjs # gate: every peer floor covered by a devDep floor
|
|
17
|
+
prepublish-gate.mjs # per-package publish gate
|
|
18
|
+
packages/
|
|
19
|
+
arc-approval/ # @classytic/arc-approval — one package, one surface
|
|
20
|
+
package.json
|
|
21
|
+
tsdown.config.ts
|
|
22
|
+
src/index.ts
|
|
23
|
+
arc-involvement/
|
|
24
|
+
arc-notifications/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Root `package.json` scripts:
|
|
28
|
+
|
|
29
|
+
```jsonc
|
|
30
|
+
{
|
|
31
|
+
"workspaces": ["packages/*"],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "npm run build --workspaces",
|
|
34
|
+
"typecheck": "npm run typecheck --workspaces",
|
|
35
|
+
"lint": "biome check packages/",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"check:peer-skew": "node scripts/check-peer-skew.mjs",
|
|
38
|
+
"verify": "npm run check:peer-skew && npm run typecheck && npm run lint && npm run test && npm run build"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Shared devDeps (arc, primitives, biome, tsdown, typescript, vitest, fastify, @types/node) live
|
|
44
|
+
in the **root** `package.json` so every package tests against one aligned set. During
|
|
45
|
+
pre-publish development the root can carry `"@classytic/arc": "file:../arc"` to test against
|
|
46
|
+
unreleased arc; **switch it to the published range (`"^2.20.0"`) once arc ships** and reinstall
|
|
47
|
+
so packages resolve arc from the registry tarball, not the local symlink.
|
|
48
|
+
|
|
49
|
+
**pnpm + turborepo + changesets** is the heavier-duty alternative to npm workspaces once the
|
|
50
|
+
package count grows: turbo gives cached, topo-ordered builds (`dependsOn: ["^build"]`);
|
|
51
|
+
changesets drive versioning + changelogs (`access: "public"`, `updateInternalDependencies:
|
|
52
|
+
"patch"`). Wire the peer-skew gate into the release path so it can't be skipped — e.g.
|
|
53
|
+
`"release": "pnpm run check:peer-skew && turbo typecheck test build && changeset publish"`.
|
|
54
|
+
Local workspace links use `workspace:*` (pnpm) instead of `file:`; the peer-skew gate treats
|
|
55
|
+
`workspace:` / `link:` / `file:` as covering any floor (the checked-out source *is* the version
|
|
56
|
+
under development).
|
|
57
|
+
|
|
58
|
+
### Supply-chain hygiene (.npmrc / pnpm-workspace.yaml)
|
|
59
|
+
|
|
60
|
+
Belt-and-suspenders settings worth adopting for a published ecosystem:
|
|
61
|
+
|
|
62
|
+
```ini
|
|
63
|
+
# .npmrc
|
|
64
|
+
auto-install-peers=false # peers are the HOST's duty — never silently fetch them
|
|
65
|
+
save-workspace-protocol=rolled
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
# pnpm-workspace.yaml
|
|
70
|
+
onlyBuiltDependencies: # allowlist packages permitted to run install scripts
|
|
71
|
+
- esbuild
|
|
72
|
+
- mongodb-memory-server
|
|
73
|
+
minimumReleaseAgeExclude: # cooldown on brand-new registry versions, with explicit escapes
|
|
74
|
+
- '@classytic/repo-core@0.7.0'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `auto-install-peers=false` enforces the contract mechanically: if a host forgets a peer, they
|
|
78
|
+
get an error, not a silently-resolved wrong version. It also stops your *dev* environment from
|
|
79
|
+
masking a missing peer declaration.
|
|
80
|
+
- `onlyBuiltDependencies` is a lifecycle-script allowlist — nothing runs `postinstall` unless you
|
|
81
|
+
named it. A cheap, real guard against a compromised transitive dep executing on `pnpm install`.
|
|
82
|
+
- A `minimumReleaseAge` cooldown blunts the "malicious version published minutes ago" attack;
|
|
83
|
+
the `Exclude` list is your escape hatch for versions you've vetted.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Gate 1 — peer-skew (monorepo)
|
|
88
|
+
|
|
89
|
+
`scripts/check-peer-skew.mjs` fails the build if any package declares a peer floor higher than
|
|
90
|
+
the devDep floor it actually tests against. Without it, a package can claim `">=2.20.0"` while
|
|
91
|
+
its suite runs against 2.18 — a lie that ships. The gate reads root + package devDeps together
|
|
92
|
+
(`file:` links count as covering any floor, since the link *is* the version under development).
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
// Core check, per package, per peer:
|
|
96
|
+
// devFloor = floorOf(devDeps[name]) // from root + package devDeps merged
|
|
97
|
+
// peerFloor = floorOf(peers[name])
|
|
98
|
+
// if (devFloor < peerFloor) FAIL // suite tests below what you promise
|
|
99
|
+
// Optional peers (peerDependenciesMeta.optional) with no devDep are allowed to be absent.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Run it first in `verify` so a skew fails fast before the slower typecheck/test/build.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Gate 2 — prepublish (per package)
|
|
107
|
+
|
|
108
|
+
No package publishes without a green gate. No `--no-verify`, ever.
|
|
109
|
+
|
|
110
|
+
- **Solo package:** `"prepublishOnly": "npm run typecheck && npm run lint && npm run test && npm run build"`.
|
|
111
|
+
- **Workspace:** a shared `scripts/prepublish-gate.mjs` invoked from each package's
|
|
112
|
+
`prepublishOnly`, running the full workspace `verify` (peer-skew + typecheck + lint + test +
|
|
113
|
+
build) so no package ships against a skewed or broken tree.
|
|
114
|
+
|
|
115
|
+
The gate is the contract with consumers: if it's green, the published `dist/` was typechecked,
|
|
116
|
+
linted, tested, and built from the source in the same commit.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Release — classytic packages (bot identity)
|
|
121
|
+
|
|
122
|
+
Every commit in a classytic package is authored as **`classytic-bot[bot]`** — the maintainer
|
|
123
|
+
identity, never an individual. **Per-invocation git env vars only; never `git config
|
|
124
|
+
--global`; no `Co-Authored-By` trailers.**
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
GIT_AUTHOR_NAME="classytic-bot[bot]" \
|
|
128
|
+
GIT_AUTHOR_EMAIL="278929599+classytic-bot[bot]@users.noreply.github.com" \
|
|
129
|
+
GIT_COMMITTER_NAME="classytic-bot[bot]" \
|
|
130
|
+
GIT_COMMITTER_EMAIL="278929599+classytic-bot[bot]@users.noreply.github.com" \
|
|
131
|
+
git commit -m "feat(arc-x): …"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Push through `@classytic/dev-tools` so the commit and pusher both carry the `[bot]` badge (it
|
|
135
|
+
mints a short-lived GitHub App installation token, pushes, and never persists it):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run push -- main # push current work to main
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Publish to **public npm** (`publishConfig.access: "public"` is already set):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm publish -w @classytic/arc-<domain> # prepublishOnly gate runs automatically
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Third-party (non-classytic) packages: normal git identity, same gates and same
|
|
148
|
+
public/scoped-npm publish — only the bot identity and `classytic-push` are classytic-specific.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Versioning a public export
|
|
153
|
+
|
|
154
|
+
SemVer the **contract**, not the internals:
|
|
155
|
+
|
|
156
|
+
| Change | Bump |
|
|
157
|
+
|---|---|
|
|
158
|
+
| New optional config field, new exported helper, new action | **minor** |
|
|
159
|
+
| Raising a peer floor (e.g. now requires arc ≥2.21) | **minor** (it narrows the host set) |
|
|
160
|
+
| Changing an exported function signature, removing an export, renaming an action/field | **major** |
|
|
161
|
+
| Internal refactor, dep bump within range, doc/test-only | **patch** |
|
|
162
|
+
|
|
163
|
+
The `ArcModule` shape you return, your exported function signatures, and your action/schema
|
|
164
|
+
surface are the contract consumers pin against. Internals (private helpers, how the engine is
|
|
165
|
+
built) are free to change under a patch.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Common publish failures (and the fix)
|
|
170
|
+
|
|
171
|
+
- **Peer written as `^` instead of `>=`** → blocks hosts on a newer major that still satisfies
|
|
172
|
+
your API. Fix: `">=X.Y.0"`.
|
|
173
|
+
- **arc/fastify in `dependencies`** → host gets a bundled duplicate; plugin registry splits.
|
|
174
|
+
Fix: move to `peerDependencies`.
|
|
175
|
+
- **Optional-peer types in `dist/*.d.mts`** → hosts without that peer fail to compile your
|
|
176
|
+
package. Fix: structural typing at the public boundary (see package-manifest.md).
|
|
177
|
+
- **Deep import (`@classytic/arc/src/...`)** → breaks on any arc internal reorg. Fix: use a
|
|
178
|
+
public subpath, or file the missing export against arc.
|
|
179
|
+
- **A back-edge: arc depending on your package** → un-publishable cycle. Fix: the abstraction
|
|
180
|
+
belongs in arc as a port/hook, not as a dependency on you.
|
|
181
|
+
- **Skewed devDep below peer floor** → suite tests a version you claim to support but don't.
|
|
182
|
+
Fix: raise the devDep, reinstall, re-run peer-skew.
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
//#region src/testing/preloadResources.ts
|
|
2
|
-
/**
|
|
3
|
-
* Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
|
|
4
|
-
*
|
|
5
|
-
* Accepts either:
|
|
6
|
-
* - `{ import: 'default' }` form: values are the resource directly
|
|
7
|
-
* - default form: values are the full module — picks first export with `toPlugin()`
|
|
8
|
-
*
|
|
9
|
-
* Throws if any module doesn't yield a valid `ResourceLike`.
|
|
10
|
-
*/
|
|
11
|
-
function preloadResources(globResult) {
|
|
12
|
-
const resources = [];
|
|
13
|
-
for (const [path, value] of Object.entries(globResult)) {
|
|
14
|
-
const resource = pickResource(value);
|
|
15
|
-
if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
16
|
-
resources.push(resource);
|
|
17
|
-
}
|
|
18
|
-
return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
|
|
22
|
-
*
|
|
23
|
-
* Use this when resources depend on prior bootstrap (e.g., engine init) and
|
|
24
|
-
* cannot be evaluated at import time of the preload file.
|
|
25
|
-
*/
|
|
26
|
-
async function preloadResourcesAsync(globResult) {
|
|
27
|
-
return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
|
|
28
|
-
const resource = pickResource(await loader());
|
|
29
|
-
if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
30
|
-
return resource;
|
|
31
|
-
}))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
32
|
-
}
|
|
33
|
-
function pickResource(value) {
|
|
34
|
-
if (!value || typeof value !== "object") return void 0;
|
|
35
|
-
if (typeof value.toPlugin === "function") return value;
|
|
36
|
-
const mod = value;
|
|
37
|
-
const candidates = [
|
|
38
|
-
mod.default,
|
|
39
|
-
mod.resource,
|
|
40
|
-
...Object.values(mod)
|
|
41
|
-
];
|
|
42
|
-
for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
|
|
43
|
-
}
|
|
44
|
-
//#endregion
|
|
45
|
-
export { preloadResourcesAsync as n, preloadResources as t };
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
//#region src/utils/schemaConverter.ts
|
|
2
|
-
/** Default target for Fastify-consumed schemas (matches Fastify v5's default AJV draft). */
|
|
3
|
-
const DEFAULT_FASTIFY_TARGET = "draft-7";
|
|
4
|
-
/** Default target for OpenAPI document generation (matches arc's emitted OpenAPI version). */
|
|
5
|
-
const DEFAULT_OPENAPI_TARGET = "openapi-3.0";
|
|
6
|
-
let _toJSONSchema = null;
|
|
7
|
-
import("zod").then(({ z }) => {
|
|
8
|
-
if (typeof z?.toJSONSchema === "function") _toJSONSchema = (schema, opts) => z.toJSONSchema(schema, opts);
|
|
9
|
-
}).catch(() => {});
|
|
10
|
-
/**
|
|
11
|
-
* Check if an object is already a plain JSON Schema.
|
|
12
|
-
* Returns true if it has JSON Schema markers (`type`, `properties`, `$ref`,
|
|
13
|
-
* `allOf`, `anyOf`, `oneOf`, `items`, `enum`) and does NOT have Zod markers.
|
|
14
|
-
*/
|
|
15
|
-
function isJsonSchema(input) {
|
|
16
|
-
if (input === null || typeof input !== "object") return false;
|
|
17
|
-
const obj = input;
|
|
18
|
-
if ("_def" in obj || "_zod" in obj) return false;
|
|
19
|
-
return "type" in obj || "properties" in obj || "$ref" in obj || "allOf" in obj || "anyOf" in obj || "oneOf" in obj || "items" in obj || "enum" in obj;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Check if an object is a Zod schema (has `_zod` marker from Zod v4).
|
|
23
|
-
*/
|
|
24
|
-
function isZodSchema(input) {
|
|
25
|
-
return input !== null && typeof input === "object" && "_zod" in input;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Convert any schema input to JSON Schema.
|
|
29
|
-
*
|
|
30
|
-
* Detection order:
|
|
31
|
-
* 1. `null`/`undefined` → `undefined`
|
|
32
|
-
* 2. Already JSON Schema → pass through as-is (zero overhead)
|
|
33
|
-
* 3. Zod v4 schema → `z.toJSONSchema(schema, { target })`
|
|
34
|
-
* 4. Unrecognized object → return as-is (treat as opaque schema)
|
|
35
|
-
*
|
|
36
|
-
* @param input Schema (Zod, plain JSON Schema, or opaque object)
|
|
37
|
-
* @param target Output target — defaults to `draft-7` for Fastify compatibility.
|
|
38
|
-
* Pass `openapi-3.0`/`openapi-3.1` for OpenAPI document generation.
|
|
39
|
-
*/
|
|
40
|
-
function toJsonSchema(input, target = DEFAULT_FASTIFY_TARGET) {
|
|
41
|
-
if (input == null) return void 0;
|
|
42
|
-
if (typeof input !== "object") return void 0;
|
|
43
|
-
if (isJsonSchema(input)) return input;
|
|
44
|
-
if (isZodSchema(input)) {
|
|
45
|
-
if (!_toJSONSchema) return input;
|
|
46
|
-
try {
|
|
47
|
-
const converted = _toJSONSchema(input, { target });
|
|
48
|
-
if ("$schema" in converted) delete converted.$schema;
|
|
49
|
-
return converted;
|
|
50
|
-
} catch {
|
|
51
|
-
return { type: "object" };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return input;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Convert all schema fields in an OpenApiSchemas object.
|
|
58
|
-
* JSON Schema values pass through unchanged. Only Zod schemas are converted.
|
|
59
|
-
*
|
|
60
|
-
* Defaults to the `openapi-3.0` target since this function feeds OpenAPI doc
|
|
61
|
-
* generation, not Fastify route validation.
|
|
62
|
-
*/
|
|
63
|
-
function convertOpenApiSchemas(schemas, target = DEFAULT_OPENAPI_TARGET) {
|
|
64
|
-
const result = {};
|
|
65
|
-
const schemaFields = [
|
|
66
|
-
"entity",
|
|
67
|
-
"createBody",
|
|
68
|
-
"updateBody",
|
|
69
|
-
"params",
|
|
70
|
-
"listQuery",
|
|
71
|
-
"response"
|
|
72
|
-
];
|
|
73
|
-
for (const field of schemaFields) {
|
|
74
|
-
const value = schemas[field];
|
|
75
|
-
if (value !== void 0) result[field] = toJsonSchema(value, target) ?? value;
|
|
76
|
-
}
|
|
77
|
-
for (const [key, value] of Object.entries(schemas)) if (!schemaFields.includes(key)) result[key] = value;
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Convert schema values in a Fastify route schema record.
|
|
82
|
-
*
|
|
83
|
-
* Handles `body`, `querystring`, `params`, `headers` (top-level conversion)
|
|
84
|
-
* and `response` (iterates by status code — each value converted individually).
|
|
85
|
-
*
|
|
86
|
-
* JSON Schema values pass through unchanged. Only Zod schemas are converted.
|
|
87
|
-
*
|
|
88
|
-
* Used for both custom routes and customSchemas (CRUD overrides).
|
|
89
|
-
*
|
|
90
|
-
* Defaults to `draft-7` so Fastify v5's bundled AJV 8 accepts the output.
|
|
91
|
-
* Pass `openapi-3.0` (or `openapi-3.1`) when generating OpenAPI documents.
|
|
92
|
-
*/
|
|
93
|
-
function convertRouteSchema(schema, target = DEFAULT_FASTIFY_TARGET) {
|
|
94
|
-
const result = { ...schema };
|
|
95
|
-
for (const field of [
|
|
96
|
-
"body",
|
|
97
|
-
"querystring",
|
|
98
|
-
"params",
|
|
99
|
-
"headers"
|
|
100
|
-
]) if (result[field] !== void 0) result[field] = toJsonSchema(result[field], target) ?? result[field];
|
|
101
|
-
if (result.response !== void 0 && typeof result.response === "object" && result.response !== null) {
|
|
102
|
-
const responseObj = result.response;
|
|
103
|
-
const convertedResponse = {};
|
|
104
|
-
for (const [statusCode, responseSchema] of Object.entries(responseObj)) convertedResponse[statusCode] = toJsonSchema(responseSchema, target) ?? responseSchema;
|
|
105
|
-
result.response = convertedResponse;
|
|
106
|
-
}
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
//#endregion
|
|
110
|
-
export { toJsonSchema as a, isZodSchema as i, convertRouteSchema as n, isJsonSchema as r, convertOpenApiSchemas as t };
|