@prestack/thread 0.0.1 → 0.0.2

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 (150) hide show
  1. package/dist/core/actor.ts +20 -0
  2. package/dist/core/asking.d.ts +0 -1
  3. package/dist/core/asking.js +0 -1
  4. package/dist/core/asking.ts +1342 -0
  5. package/dist/core/attach.js +2 -2
  6. package/dist/core/attach.ts +585 -0
  7. package/dist/core/chain.js +8 -1
  8. package/dist/core/chain.ts +148 -0
  9. package/dist/core/cli.js +97 -18
  10. package/dist/core/cli.ts +2004 -0
  11. package/dist/core/condition.ts +227 -0
  12. package/dist/core/descend.d.ts +1 -0
  13. package/dist/core/descend.js +36 -0
  14. package/dist/core/descend.ts +1001 -0
  15. package/dist/core/finding.ts +38 -0
  16. package/dist/core/generators/meta.js +6 -0
  17. package/dist/core/generators/meta.ts +1006 -0
  18. package/dist/core/init.ts +170 -0
  19. package/dist/core/invoked.ts +47 -0
  20. package/dist/core/round.ts +57 -0
  21. package/dist/core/rows.ts +369 -0
  22. package/dist/core/scope.ts +18 -0
  23. package/dist/core/thread.ts +29 -0
  24. package/dist/core/tree.ts +67 -0
  25. package/dist/core/types.ts +350 -0
  26. package/dist/derivation/attach.js +1 -1
  27. package/dist/derivation/attach.ts +68 -0
  28. package/dist/derivation/generators/build.ts +232 -0
  29. package/dist/derivation/generators/check.ts +118 -0
  30. package/dist/derivation/generators/derivation.ts +347 -0
  31. package/dist/derivation/generators/determinism.ts +145 -0
  32. package/dist/derivation/generators/release.js +19 -9
  33. package/dist/derivation/generators/release.ts +213 -0
  34. package/dist/derivation/makers/release.ts +135 -0
  35. package/dist/derivation/release.ts +23 -0
  36. package/dist/integrations/attach.js +2 -2
  37. package/dist/integrations/attach.ts +207 -0
  38. package/dist/integrations/generators/read.ts +127 -0
  39. package/dist/integrations/generators/secret.ts +289 -0
  40. package/dist/integrations/generators/target.js +16 -6
  41. package/dist/integrations/generators/target.ts +944 -0
  42. package/dist/integrations/generators/vault.js +46 -14
  43. package/dist/integrations/generators/vault.ts +212 -0
  44. package/dist/integrations/{sdk.js → module.js} +4 -4
  45. package/dist/integrations/module.ts +189 -0
  46. package/dist/integrations/secret.ts +50 -0
  47. package/dist/integrations/target.d.ts +3 -1
  48. package/dist/integrations/target.js +14 -11
  49. package/dist/integrations/target.ts +340 -0
  50. package/dist/integrations/vault.d.ts +2 -0
  51. package/dist/integrations/vault.js +4 -2
  52. package/dist/integrations/vault.ts +136 -0
  53. package/dist/integrity/attach.ts +234 -0
  54. package/dist/integrity/check-threads.js +2 -26
  55. package/dist/integrity/check-threads.ts +166 -0
  56. package/dist/integrity/constraint.ts +1202 -0
  57. package/dist/integrity/generators/compiler.ts +161 -0
  58. package/dist/integrity/generators/gate.ts +201 -0
  59. package/dist/integrity/generators/issues.ts +160 -0
  60. package/dist/integrity/generators/publish.js +14 -5
  61. package/dist/integrity/generators/publish.ts +247 -0
  62. package/dist/integrity/generators/rule.ts +271 -0
  63. package/dist/integrity/generators/seal.ts +117 -0
  64. package/dist/integrity/generators/trial.ts +320 -0
  65. package/dist/integrity/lint.ts +227 -0
  66. package/dist/integrity/lsp/bin.ts +42 -0
  67. package/dist/integrity/lsp/diagnostics.ts +92 -0
  68. package/dist/integrity/lsp/findings.ts +63 -0
  69. package/dist/integrity/lsp/lint.ts +140 -0
  70. package/dist/integrity/lsp/protocol.ts +78 -0
  71. package/dist/integrity/lsp/server.ts +224 -0
  72. package/dist/integrity/sealed.js +2 -2
  73. package/dist/integrity/sealed.ts +56 -0
  74. package/dist/integrity/slopnator.ts +350 -0
  75. package/dist/integrity/slopnators/sl_broken_lineage.ts +36 -0
  76. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +39 -0
  77. package/dist/integrity/slopnators/sl_derived_drift.ts +98 -0
  78. package/dist/integrity/slopnators/sl_hardcoded_style.ts +55 -0
  79. package/dist/integrity/slopnators/sl_inert_island.ts +91 -0
  80. package/dist/integrity/slopnators/sl_inert_slopnator.ts +93 -0
  81. package/dist/integrity/slopnators/sl_missing_row.ts +182 -0
  82. package/dist/integrity/slopnators/sl_no_barrel.ts +48 -0
  83. package/dist/integrity/slopnators/sl_no_child_process.ts +74 -0
  84. package/dist/integrity/slopnators/sl_no_comment.ts +43 -0
  85. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +328 -0
  86. package/dist/integrity/slopnators/sl_no_import_cycle.ts +50 -0
  87. package/dist/integrity/slopnators/sl_no_such_command.ts +64 -0
  88. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +63 -0
  89. package/dist/integrity/slopnators/sl_secret_literal.ts +100 -0
  90. package/dist/integrity/slopnators/sl_stale_path.ts +94 -0
  91. package/dist/integrity/slopnators/sl_twice_meant.ts +31 -0
  92. package/dist/integrity/slopnators/sl_twice_parented.ts +37 -0
  93. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +87 -0
  94. package/dist/integrity/slopnators/sl_undeclared_destination.ts +65 -0
  95. package/dist/integrity/slopnators/sl_undeclared_element.ts +44 -0
  96. package/dist/integrity/slopnators/sl_undeclared_node.ts +69 -0
  97. package/dist/integrity/slopnators/sl_undeclared_operation.ts +147 -0
  98. package/dist/integrity/slopnators/sl_undeclared_variant.ts +148 -0
  99. package/dist/integrity/slopnators/sl_undescribed.ts +65 -0
  100. package/dist/integrity/slopnators/sl_ungoverned_island.ts +61 -0
  101. package/dist/integrity/slopnators/sl_unkept_rows.ts +111 -0
  102. package/dist/integrity/slopnators/sl_unnamed_island.ts +41 -0
  103. package/dist/integrity/slopnators/sl_unpaired_member.ts +131 -0
  104. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +58 -0
  105. package/dist/integrity/slopnators/sl_unprefixed_member.ts +136 -0
  106. package/dist/integrity/slopnators/sl_unseamed_reach.ts +79 -0
  107. package/dist/integrity/slopnators/sl_unthreaded.ts +419 -0
  108. package/dist/integrity/slopnators/sl_untranslated_key.ts +164 -0
  109. package/dist/integrity/slopnators/sl_unversioned.ts +36 -0
  110. package/dist/integrity/sweep.ts +307 -0
  111. package/dist/knowledge/attach.ts +350 -0
  112. package/dist/knowledge/generators/docsgen.ts +174 -0
  113. package/dist/knowledge/generators/glossary.ts +73 -0
  114. package/dist/knowledge/generators/skills.ts +263 -0
  115. package/dist/knowledge/glossary.ts +6 -0
  116. package/dist/operations/access.ts +88 -0
  117. package/dist/operations/attach.ts +475 -0
  118. package/dist/operations/capabilities.ts +65 -0
  119. package/dist/operations/cost.ts +58 -0
  120. package/dist/operations/dispatch.ts +348 -0
  121. package/dist/operations/error-results.ts +31 -0
  122. package/dist/operations/errors/catalog.ts +168 -0
  123. package/dist/operations/errors/classes.ts +51 -0
  124. package/dist/operations/errors/errors.ts +314 -0
  125. package/dist/operations/generators/capability.js +29 -19
  126. package/dist/operations/generators/capability.ts +704 -0
  127. package/dist/operations/generators/compile.js +20 -1
  128. package/dist/operations/generators/compile.ts +643 -0
  129. package/dist/operations/generators/grant.ts +336 -0
  130. package/dist/operations/generators/operation.ts +972 -0
  131. package/dist/operations/generators/permits.ts +223 -0
  132. package/dist/operations/generators/seed.ts +347 -0
  133. package/dist/operations/generators/trial.js +4 -1
  134. package/dist/operations/generators/trial.ts +529 -0
  135. package/dist/operations/http.ts +164 -0
  136. package/dist/operations/makers/capability.ts +166 -0
  137. package/dist/operations/makers/trial.ts +520 -0
  138. package/dist/operations/memory.ts +470 -0
  139. package/dist/operations/metering.ts +328 -0
  140. package/dist/operations/nodes.ts +54 -0
  141. package/dist/operations/operations.ts +32 -0
  142. package/dist/operations/queue-policy.ts +10 -0
  143. package/dist/operations/result.ts +28 -0
  144. package/dist/operations/seams.d.ts +4 -4
  145. package/dist/operations/seams.ts +364 -0
  146. package/dist/operations/store.ts +29 -0
  147. package/package.json +8 -1
  148. package/dist/integrations/generators/sdk.d.ts +0 -1
  149. package/dist/integrations/generators/sdk.js +0 -148
  150. /package/dist/integrations/{sdk.d.ts → module.d.ts} +0 -0
@@ -0,0 +1,364 @@
1
+ import type { Actor } from '../core/actor.ts'
2
+
3
+ export type { FieldList, ReadField, Row, Store } from './store.ts'
4
+
5
+ import type { FieldList, Row, Store } from './store.ts'
6
+
7
+ export interface Span {
8
+ ok(): void
9
+ error(e: unknown): void
10
+ end(): void
11
+ }
12
+ export interface Telemetry {
13
+ start(action: string, run: string, tags: Record<string, unknown>): Span
14
+
15
+ record?(span: {
16
+ readonly key: string
17
+ readonly traceId: string
18
+ readonly spanId: string
19
+ readonly durationMs: number
20
+ readonly ok: boolean
21
+ readonly attrs?: Record<string, unknown>
22
+
23
+ readonly actor?: Actor
24
+
25
+ readonly nodeId?: string
26
+
27
+ readonly workflowId?: string
28
+
29
+ readonly error?: string
30
+ }): void
31
+ }
32
+
33
+ export type PropertyValue = string | number | boolean | null | readonly (string | number | boolean)[]
34
+
35
+ export type LogLevel = 'info' | 'warn' | 'error'
36
+
37
+ export interface Log {
38
+ write(level: LogLevel, action: string, actor: Actor, data: Record<string, unknown>): void
39
+ }
40
+
41
+ export interface AuditRecord {
42
+ readonly action: string
43
+ readonly outcome: AuditOutcome
44
+
45
+ readonly subject: { type: string; id: string }
46
+
47
+ readonly changed?: readonly string[]
48
+
49
+ readonly because?: string
50
+ }
51
+
52
+ export interface Audit {
53
+ append(actor: Actor, record: AuditRecord): Promise<void>
54
+ }
55
+
56
+ export interface ErrorRecord {
57
+ readonly code: string
58
+ readonly class: string
59
+ readonly action: string
60
+ readonly because?: string
61
+ }
62
+
63
+ export interface Errors {
64
+ record(actor: Actor, error: ErrorRecord): Promise<void>
65
+ }
66
+
67
+ export interface Search {
68
+ index(actor: Actor, entity: string, row: Row): Promise<void>
69
+ query(actor: Actor, entity: string, q: string): Promise<Row[]>
70
+ }
71
+
72
+ export interface Views {
73
+ query<T = Record<string, unknown>>(
74
+ actor: Actor,
75
+ sql: string,
76
+ params: readonly unknown[],
77
+ ): Promise<readonly T[]>
78
+ }
79
+
80
+ export interface Cost {
81
+ meter(action: string, actor: Actor, units?: number): void
82
+ }
83
+
84
+ export interface Idempotency {
85
+ check(key: string | undefined, action: string): Promise<unknown | null>
86
+ store(key: string | undefined, action: string, output: unknown): Promise<void>
87
+ }
88
+
89
+ export interface PlatformEvent {
90
+ kind: string
91
+ entity?: string
92
+ id?: string
93
+ run?: string
94
+ actor: string | null
95
+ at: number
96
+
97
+ payload?: Readonly<Record<string, unknown>>
98
+ }
99
+ export interface EventBus {
100
+ publish(event: PlatformEvent): Promise<void>
101
+ }
102
+
103
+ export interface RateLimiter {
104
+ check(actor: Actor, action: string, operation?: string): Promise<boolean>
105
+
106
+ checkPolicy(actor: Actor, policy: string): Promise<boolean>
107
+ }
108
+
109
+ export interface FeatureGate {
110
+ check(actor: Actor, feature: string): Promise<boolean>
111
+ }
112
+
113
+ export interface QuotaHold {
114
+ readonly ok: boolean
115
+
116
+ readonly behavior: string | null
117
+ readonly retryAfterSeconds: number
118
+
119
+ commit(actual: number): Promise<void>
120
+
121
+ release(): Promise<void>
122
+ }
123
+
124
+ export interface Quotas {
125
+ reserve(actor: Actor, meter: string, estimate: number): Promise<QuotaHold>
126
+
127
+ lower?(actor: Actor, meter: string, amount: number): Promise<void>
128
+ }
129
+
130
+ export interface Outbox {
131
+ emit(actor: Actor, event: PlatformEvent): Promise<void>
132
+ }
133
+
134
+ export interface Transactional {
135
+ transaction<T>(actor: Actor, fn: () => Promise<T>): Promise<T>
136
+ }
137
+
138
+ export function isTransactional(store: Store): store is Store & Transactional {
139
+ return typeof (store as Partial<Transactional>).transaction === 'function'
140
+ }
141
+
142
+ export interface Job<T = unknown> {
143
+ id: string
144
+ kind: string
145
+ payload: T
146
+ attempts: number
147
+
148
+ actor: Actor
149
+ }
150
+
151
+ export interface EnqueueOpts {
152
+
153
+ runAt?: number
154
+
155
+ dedupeKey?: string
156
+ }
157
+
158
+ export interface SchedulerSeam {
159
+
160
+ readonly endpoint: string
161
+
162
+ readonly secret: string
163
+
164
+ register(app: { id: string; endpoint: string; secret: string }): Promise<void>
165
+ schedule(call: {
166
+ appId: string
167
+ runAt: Date
168
+ reason: string
169
+ runId?: string
170
+
171
+ dedupeKey?: string
172
+ }): Promise<string>
173
+ cancelForRun(appId: string, runId: string): Promise<void>
174
+ }
175
+
176
+ export interface Queue {
177
+ enqueue<T>(actor: Actor, kind: string, payload: T, opts?: EnqueueOpts): Promise<string>
178
+
179
+ poll(kinds: string[], limit: number, now: number): Promise<Job[]>
180
+
181
+ ack(id: string): Promise<void>
182
+
183
+ fail(id: string, error: string, now: number): Promise<void>
184
+
185
+ deadLetter(id: string, error: string): Promise<void>
186
+
187
+ deadLetters(): Promise<Job[]>
188
+ }
189
+
190
+ export interface Files {
191
+
192
+ put(actor: Actor, key: string, body: Uint8Array, contentType: string): Promise<{ size: number }>
193
+
194
+ get(actor: Actor, key: string): Promise<{ body: Uint8Array; contentType: string } | null>
195
+
196
+ signedUrl(actor: Actor, key: string, expiresInSeconds: number): Promise<string>
197
+
198
+ remove(actor: Actor, key: string): Promise<void>
199
+ }
200
+
201
+ export interface Clock {
202
+
203
+ now(): number
204
+ }
205
+
206
+ export interface Random {
207
+
208
+ next(): number
209
+
210
+ uuid(): string
211
+ }
212
+
213
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
214
+
215
+ export interface HttpRequest {
216
+
217
+ readonly target: string
218
+
219
+ readonly address: string
220
+ readonly method: HttpMethod
221
+
222
+ readonly path: string
223
+
224
+ readonly headers?: Readonly<Record<string, string>>
225
+
226
+ readonly body?: Uint8Array
227
+ readonly contentType?: string
228
+
229
+ readonly idempotencyKey?: string
230
+ }
231
+
232
+ export interface HttpResponse {
233
+ readonly status: number
234
+ readonly headers: Readonly<Record<string, string>>
235
+ readonly body: Uint8Array
236
+
237
+ readonly bytesSent: number
238
+ readonly bytesReceived: number
239
+ readonly durationMs: number
240
+ }
241
+
242
+ export interface Http {
243
+
244
+ call(request: HttpRequest): Promise<HttpResponse>
245
+ }
246
+
247
+ export interface Vault {
248
+
249
+ resolve(refs: readonly string[]): Promise<Readonly<Record<string, string>>>
250
+ }
251
+
252
+ export interface Seams {
253
+
254
+ store?: Store
255
+ search?: Search
256
+ events?: EventBus
257
+ queue?: Queue
258
+
259
+ telemetry?: Telemetry
260
+ log: Log
261
+ audit: Audit
262
+ errors: Errors
263
+ cost?: Cost
264
+ idempotency?: Idempotency
265
+ rateLimiter?: RateLimiter
266
+
267
+ flush?: () => Promise<void>
268
+
269
+ scheduler?: SchedulerSeam
270
+
271
+ featureGate?: FeatureGate
272
+
273
+ permits?: {
274
+ check(
275
+ actor: Actor,
276
+ action: string,
277
+ resource: Row | null,
278
+ ): Promise<{ readonly allow: boolean; readonly because?: string }>
279
+ }
280
+ quotas?: Quotas
281
+ outbox?: Outbox
282
+
283
+ drainKick?: () => void
284
+
285
+ vault?: Vault
286
+
287
+ views?: Views
288
+
289
+ clock: Clock
290
+ random: Random
291
+
292
+ files?: Files
293
+
294
+ http?: Http
295
+ }
296
+
297
+ export type AuditOutcome = 'ok' | 'denied' | 'rate_limited' | 'failed' | 'feature_denied' | 'quota_exceeded'
298
+
299
+ export function isRow(v: unknown): v is Row {
300
+ return typeof v === 'object' && v !== null && typeof (v as { id?: unknown }).id === 'string'
301
+ }
302
+
303
+ export const SUPPLY = {
304
+ sm_store: 'store',
305
+ sm_search: 'search',
306
+ sm_queue: 'queue',
307
+ sm_events: 'events',
308
+ sm_clock: 'clock',
309
+ sm_random: 'random',
310
+ sm_vault: 'vault',
311
+ sm_views: 'views',
312
+ sm_files: 'files',
313
+ } as const satisfies Readonly<Record<string, keyof Seams>>
314
+
315
+ export type Supplied = keyof typeof SUPPLY
316
+
317
+ export const SUPPLIES: readonly Supplied[] = Object.keys(SUPPLY) as readonly Supplied[]
318
+
319
+ export const supplies = (one: string): one is Supplied => one in SUPPLY
320
+
321
+ export type Handed<R extends string = Supplied> = { readonly actor: Actor } & {
322
+ readonly [K in R as K extends Supplied ? (typeof SUPPLY)[K] : K]: K extends Supplied
323
+ ? NonNullable<Seams[(typeof SUPPLY)[K]]>
324
+ : Readonly<
325
+ Record<
326
+ string,
327
+ (given?: {
328
+ readonly fills?: Readonly<Record<string, string | number>>
329
+ readonly json?: unknown
330
+ readonly body?: Uint8Array
331
+ readonly contentType?: string
332
+ }) => Promise<HttpResponse>
333
+ >
334
+ >
335
+ }
336
+
337
+ export type Reads<H> = Pick<H, Extract<keyof H, 'actor' | 'store' | 'search' | 'views'>>
338
+
339
+ export const SAID: Readonly<Record<keyof Seams, string>> = {
340
+ store: 'where rows are kept: insert, get, list, update and remove, bound to whoever asked',
341
+ search: 'indexing a row and querying an index, for what cannot be found by id',
342
+ events: 'announcing that something happened, to whoever is listening',
343
+ queue: 'handing work to something else to run later',
344
+ telemetry: 'the span opened around every run, and where it is recorded',
345
+ log: 'the line written for every run: its outcome, who was allowed, what it spent',
346
+ audit: 'the record of what was done, by whom, to what',
347
+ errors: 'the record of every failure, by code and class',
348
+ cost: 'metering what a run spent',
349
+ idempotency: 'remembering an answer under its key, so a repeated request is answered once',
350
+ rateLimiter: 'refusing a caller who asks too often',
351
+ flush: 'what runs when the process is about to stop, so nothing buffered is lost',
352
+ scheduler: 'registering this app with a scheduler and booking a later run',
353
+ featureGate: 'refusing an operation the caller\'s plan does not include',
354
+ permits: 'the gate every operation passes: who may ask for what, from the grants',
355
+ quotas: 'reserving what a run may spend before it starts, and settling it after',
356
+ outbox: 'events written with the transaction and published after it commits',
357
+ drainKick: 'nudging the queue worker that work has arrived',
358
+ vault: 'opening a secret by name at the moment of use',
359
+ views: 'reading through a declared view rather than a table',
360
+ clock: 'the current time',
361
+ random: 'randomness: a number, a uuid',
362
+ files: 'putting a file somewhere, fetching one back, handing out a signed link to one',
363
+ http: 'how a call leaves the process, and what it comes back with',
364
+ }
@@ -0,0 +1,29 @@
1
+ import type { Actor } from '../core/actor.ts'
2
+
3
+ export type Row = Record<string, unknown>
4
+
5
+ export type ReadField = `fld_${string}` | `rel_${string}` | (string & {})
6
+
7
+ export type FieldList = readonly [ReadField, ...ReadField[]]
8
+
9
+ export interface Store {
10
+ insert(actor: Actor, entity: string, row: Row): Promise<Row>
11
+
12
+ get(actor: Actor, entity: string, key: string | number, fields: FieldList): Promise<Row | null>
13
+
14
+ list(
15
+ actor: Actor,
16
+ entity: string,
17
+ opts?: { limit?: number; where?: Readonly<Record<string, string | number | boolean | null>> },
18
+ ): Promise<Row[]>
19
+
20
+ update(
21
+ actor: Actor,
22
+ entity: string,
23
+ key: string | number,
24
+ patch: Record<string, unknown>,
25
+ expectedVersion?: number,
26
+ ): Promise<Row>
27
+
28
+ remove(actor: Actor, entity: string, key: string | number): Promise<void>
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prestack/thread",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -12,5 +12,12 @@
12
12
  },
13
13
  "bin": {
14
14
  "thread": "dist/core/cli.js"
15
+ },
16
+ "dependencies": {
17
+ "esbuild": "^0.28.2",
18
+ "eslint": "^10.9.1",
19
+ "typescript": "^6.0.0",
20
+ "typescript-eslint": "^8.68.0",
21
+ "zod": "^4.5.4"
15
22
  }
16
23
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,148 +0,0 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
- import { spawnSync } from 'node:child_process';
10
- import { readFileSync, writeFileSync } from 'node:fs';
11
- import { relative } from 'node:path';
12
- import { stdout } from 'node:process';
13
- import { generators } from "../../core/chain.js";
14
- import { exit } from "../../core/invoked.js";
15
- import { asking, given, nouns, refuse, slug, wordsIn } from "../../core/asking.js";
16
- import { listBounds, withRow } from "../../core/rows.js";
17
- import { named as packageOf } from "../../core/scope.js";
18
- import { findTree, inTree } from "../../core/tree.js";
19
- const TREE = findTree();
20
- const FILE = 'derivation.ts';
21
- const LIST = 'GENERATORS';
22
- const OWNER = 'integrations';
23
- const DECLARES = 'declares';
24
- const NAME = /^[a-z][a-z0-9_]*$/;
25
- const PACKAGE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
26
- const PREFIX = 'sdk_';
27
- const here = (one) => relative(TREE.at, one).split('\\').join('/');
28
- const path = inTree(TREE, FILE);
29
- const at = here(path);
30
- import { quoted } from "../../core/rows.js";
31
- const words = wordsIn([]);
32
- const rows = await generators(TREE);
33
- const already = rows.filter((one) => typeof one.id === 'string' && one.id.startsWith(PREFIX));
34
- const first = words[0];
35
- if (first === undefined) {
36
- stdout.write(already.length === 0
37
- ? 'sdk: no SDK declared yet.\n'
38
- : `sdk: ${String(already.length)} declared:\n${already
39
- .map((one) => ` ${(one.by ?? '').slice('thread run '.length)} — ${one.describes ?? ''}`)
40
- .join('\n')}\n`);
41
- stdout.write('\nthread create sdk <name> — installs an npm package, asks what it is for and which declared secret it\n' +
42
- 'needs, and declares it. Then `thread run <name> <method>` calls it, resolving the credential through the\n' +
43
- 'vault at the moment of the call. `<method>` is dot-separated for a namespaced one, e.g. `customers.list`.\n');
44
- exit(0);
45
- }
46
- const name = slug(first);
47
- if (name === '')
48
- refuse('an SDK has to be called something — `thread create sdk <name>`');
49
- if (!NAME.test(name))
50
- refuse(`"${name}" cannot be the name of an SDK — it is written in lower_snake_case`);
51
- const claimed = nouns(rows, 'run').get(name);
52
- if (claimed !== undefined) {
53
- refuse(`\`thread run ${name}\` already resolves to ${claimed} — that name is taken`);
54
- }
55
- const { ask, shut } = asking(given());
56
- const npmPackage = (await ask('package', 'npm package to install: ')).trim();
57
- if (npmPackage === '') {
58
- shut();
59
- refuse('an SDK installs something — the npm package is what `npm install` is given');
60
- }
61
- if (!PACKAGE.test(npmPackage)) {
62
- shut();
63
- refuse(`"${npmPackage}" does not read as an npm package name`);
64
- }
65
- const describes = (await ask('describes', 'what is it for? ')).trim();
66
- if (describes === '') {
67
- shut();
68
- refuse('an SDK says what it is for — the sentence is what somebody reads before running it');
69
- }
70
- let secrets = [];
71
- try {
72
- const taken = (await import(__rewriteRelativeImportExtension(new URL(`file://${inTree(TREE, 'secrets.ts')}`).href)));
73
- secrets = taken.SECRETS ?? [];
74
- }
75
- catch {
76
- secrets = [];
77
- }
78
- const carrying = (await ask('requires', `which secret does it need — ${secrets.map((one) => one.id).join(', ') || '(none declared yet)'}\n` +
79
- ' If this SDK needs a url, a key, a token or a password to work, IT GOES HERE and nowhere else.\n' +
80
- ' Declare it first with `thread create secret`, then name it here, comma-separated. It is resolved\n' +
81
- ' through the vault at the moment of the call and is never written into any file.\n' +
82
- ' Blank only if the SDK genuinely needs no credential at all — a declaration with the wrong answer\n' +
83
- ' here does not fail now, it fails at the call with the vendor\'s own error.\n' +
84
- ' > '))
85
- .split(',')
86
- .map((one) => one.trim())
87
- .filter((one) => one !== '');
88
- shut();
89
- for (const one of carrying) {
90
- if (!secrets.some((each) => each.id === one)) {
91
- refuse(`\`${one}\` is not a secret this app declares. Declare it first with \`thread create secret\` — a credential is given to something that said it needed it, or not at all.`);
92
- }
93
- }
94
- let text;
95
- try {
96
- text = readFileSync(path, 'utf8');
97
- }
98
- catch {
99
- refuse(`${at} could not be read. Attach \`${packageOf('derivation')}\` first — that is what writes it.`);
100
- }
101
- const where = listBounds(text, LIST);
102
- if (where === undefined)
103
- refuse(`${at} states no \`${LIST}\` list to declare a generator in`);
104
- stdout.write(`sdk: installing ${npmPackage} …\n`);
105
- const installed = spawnSync('npm', ['install', npmPackage], { cwd: TREE.at, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
106
- if (installed.status !== 0) {
107
- refuse(`\`npm install ${npmPackage}\` failed, so nothing was declared:\n${`${installed.stdout ?? ''}${installed.stderr ?? ''}`.trim()}`);
108
- }
109
- stdout.write(`sdk: installed ${npmPackage}.\n`);
110
- const fileAt = `_threads/sdk_${name}.ts`;
111
- const SOURCE = `import { run } from '../sdk.ts'
112
-
113
- await run({ name: ${quoted(name)}, module: ${quoted(npmPackage)}, requires: [${carrying.map(quoted).join(', ')}] })
114
- `;
115
- writeFileSync(inTree(TREE, `sdk_${name}.ts`), SOURCE, 'utf8');
116
- const row = [
117
- ' {',
118
- ` id: ${quoted(`${PREFIX}${name}`)},`,
119
- ` at: ${quoted(fileAt)},`,
120
- ` describes: ${quoted(describes)},`,
121
- ' version: 1,',
122
- ` by: ${quoted(`thread run ${name}`)},`,
123
- ...(carrying.length === 0 ? [] : [` requires: [${carrying.map(quoted).join(', ')}],`]),
124
- ' },',
125
- ].join('\n');
126
- writeFileSync(path, withRow(text, where, row), 'utf8');
127
- const owner = inTree(TREE, `${OWNER}.ts`);
128
- let owned = '';
129
- try {
130
- owned = readFileSync(owner, 'utf8');
131
- }
132
- catch {
133
- refuse(`${OWNER}.ts could not be read, so there is nothing to declare this under.`);
134
- }
135
- const under = listBounds(owned, DECLARES);
136
- if (under === undefined)
137
- refuse(`${OWNER}.ts states no \`${DECLARES}\` list to declare this in`);
138
- writeFileSync(owner, withRow(owned, under, [
139
- ' {',
140
- ` id: ${quoted(`${PREFIX}${name}`)},`,
141
- ` at: ${quoted(fileAt)},`,
142
- ` describes: ${quoted(describes)},`,
143
- ' version: 1,',
144
- ' },',
145
- ].join('\n')), 'utf8');
146
- stdout.write(`sdk: declared ${name} — ${npmPackage}, reached with \`thread run ${name} <method>\`. ` +
147
- `${carrying.length === 0 ? 'It needs no secret.' : `It resolves ${carrying.join(', ')} through the vault at call time.`}\n`);
148
- exit(0);
File without changes