@listen-fire/movement-lang 0.12.0-rc.2 → 0.12.0-rc.6

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 (98) hide show
  1. package/dist/{checker/catalog.d.ts → catalog/types.d.ts} +133 -451
  2. package/dist/catalog/types.js +284 -0
  3. package/dist/checker/diagnostic.d.ts +21 -0
  4. package/dist/checker/diagnostic.js +7 -0
  5. package/dist/checker/fixes.d.ts +4 -4
  6. package/dist/checker/handbook_pointers.js +1 -1
  7. package/dist/checker/linked_types.d.ts +23 -0
  8. package/dist/checker/linked_types.js +3 -0
  9. package/dist/checker/rust_checker.d.ts +63 -1
  10. package/dist/checker/rust_checker.js +91 -4
  11. package/dist/checker/standard_library.d.ts +21 -68
  12. package/dist/checker/standard_library.js +57 -165
  13. package/dist/expression/bridge.d.ts +1 -1
  14. package/dist/expression/bridge.js +3 -3
  15. package/dist/expression/file_value.d.ts +3 -0
  16. package/dist/expression/file_value.js +3 -0
  17. package/dist/expression/filter.d.ts +100 -0
  18. package/dist/expression/filter.js +489 -0
  19. package/dist/expression/formula.d.ts +190 -0
  20. package/dist/expression/formula.js +1824 -0
  21. package/dist/expression/order_limit.d.ts +68 -0
  22. package/dist/expression/order_limit.js +156 -0
  23. package/dist/expression/types.d.ts +527 -0
  24. package/dist/expression/types.js +108 -0
  25. package/dist/index.d.ts +5 -23
  26. package/dist/index.js +4 -22
  27. package/dist/language_version.d.ts +1 -9
  28. package/dist/language_version.js +3 -20
  29. package/dist/parser/ast.d.ts +1 -1
  30. package/dist/parser/expression/lower.d.ts +21 -1
  31. package/dist/parser/expression/lower.js +34 -13
  32. package/dist/parser/parse.d.ts +20 -1
  33. package/dist/parser/parse.js +24 -9
  34. package/dist/parser/rust_parser.d.ts +29 -5
  35. package/dist/parser/rust_parser.js +56 -33
  36. package/dist/parser/surface.js +3 -2
  37. package/dist/service/snapshot.d.ts +3 -3
  38. package/dist/service/snapshot.js +9 -8
  39. package/dist/wasm/env_services.d.ts +1 -1
  40. package/dist/wasm/env_services.js +2 -2
  41. package/dist/wasm/loader.d.ts +34 -0
  42. package/dist/wasm/loader.js +51 -8
  43. package/dist/wasm/movement.wasm +0 -0
  44. package/dist/wasm/movement_parser.wasm +0 -0
  45. package/package.json +5 -15
  46. package/dist/checker/call_cycles.d.ts +0 -56
  47. package/dist/checker/call_cycles.js +0 -131
  48. package/dist/checker/calls.d.ts +0 -113
  49. package/dist/checker/calls.js +0 -350
  50. package/dist/checker/catalog.js +0 -701
  51. package/dist/checker/check.d.ts +0 -1197
  52. package/dist/checker/check.js +0 -13980
  53. package/dist/checker/collection_config.d.ts +0 -60
  54. package/dist/checker/collection_config.js +0 -173
  55. package/dist/checker/conformance.d.ts +0 -99
  56. package/dist/checker/conformance.js +0 -216
  57. package/dist/checker/effects.d.ts +0 -122
  58. package/dist/checker/effects.js +0 -194
  59. package/dist/checker/event_address.d.ts +0 -120
  60. package/dist/checker/event_address.js +0 -274
  61. package/dist/checker/extract_config.d.ts +0 -26
  62. package/dist/checker/extract_config.js +0 -106
  63. package/dist/checker/extract_keyword_retired.d.ts +0 -27
  64. package/dist/checker/extract_keyword_retired.js +0 -156
  65. package/dist/checker/flow.d.ts +0 -20
  66. package/dist/checker/flow.js +0 -59
  67. package/dist/checker/generics.d.ts +0 -49
  68. package/dist/checker/generics.js +0 -85
  69. package/dist/checker/link.d.ts +0 -74
  70. package/dist/checker/link.js +0 -226
  71. package/dist/checker/meta.d.ts +0 -40
  72. package/dist/checker/meta.js +0 -148
  73. package/dist/checker/nested_calls.d.ts +0 -80
  74. package/dist/checker/nested_calls.js +0 -259
  75. package/dist/checker/scopes.d.ts +0 -270
  76. package/dist/checker/scopes.js +0 -141
  77. package/dist/checker/typed_spelling.d.ts +0 -33
  78. package/dist/checker/typed_spelling.js +0 -188
  79. package/dist/checker/typing.d.ts +0 -2449
  80. package/dist/checker/typing.js +0 -7397
  81. package/dist/checker/upgrade.d.ts +0 -63
  82. package/dist/checker/upgrade.js +0 -261
  83. package/dist/expression/stdlib.d.ts +0 -243
  84. package/dist/expression/stdlib.js +0 -1067
  85. package/dist/parser/duration.d.ts +0 -11
  86. package/dist/parser/duration.js +0 -37
  87. package/dist/service/analysis.d.ts +0 -50
  88. package/dist/service/analysis.js +0 -245
  89. package/dist/service/constructions.d.ts +0 -116
  90. package/dist/service/constructions.js +0 -473
  91. package/dist/service/selectors.d.ts +0 -66
  92. package/dist/service/selectors.js +0 -714
  93. package/dist/service/service.d.ts +0 -77
  94. package/dist/service/service.js +0 -1865
  95. package/dist/service/surface_v5.d.ts +0 -24
  96. package/dist/service/surface_v5.js +0 -117
  97. package/dist/story/story.d.ts +0 -765
  98. package/dist/story/story.js +0 -1566
@@ -1,8 +1,5 @@
1
- import type { EdgeCapability, EdgeSequencing, Expression, FieldCapability } from '@listen-fire/shared/expression/types';
2
- import { type LanguageVersion } from '../language_version';
3
- import type { CallArg, NamedArg, PluginCall, Program, Span, TypeDeclaration } from '../parser/ast';
4
- import type { DeclaredEffectRow } from './effects';
5
- import { type PositionTypeRef } from './typing';
1
+ import type { LanguageVersion } from '../language_version';
2
+ import type { EdgeCapability, EdgeSequencing, Expression, FieldCapability } from '../expression/types';
6
3
  /**
7
4
  * One argument in an adapter's construction signature. The list is the full
8
5
  * signature: the credential (when the adapter needs one) is just an entry with
@@ -97,7 +94,7 @@ export interface AdapterSpec {
97
94
  /**
98
95
  * Per-key value FORMATS the checker validates statically. `'cron'`
99
96
  * validates the value as a five-field cron expression; `'timezone'`
100
- * validates it as an IANA time-zone id (@listen-fire/shared/cron — the same
97
+ * validates it as an IANA time-zone id (movement-lang/cron — the same
101
98
  * parser the platform scheduler runs, so check time and fire time can
102
99
  * never disagree).
103
100
  *
@@ -153,6 +150,9 @@ export interface AdapterSpec {
153
150
  * reads it.
154
151
  */
155
152
  schemaShape?: 'static' | 'introspected';
153
+ /** What the adapter says it is (its manifest's `description`). The checker
154
+ * never reads it; the language service's hover leads with it. */
155
+ description?: string;
156
156
  }
157
157
  /**
158
158
  * The credential arg in a construction signature, if the adapter needs one.
@@ -179,51 +179,6 @@ export declare function credentialArgOf(spec: Pick<AdapterSpec, 'constructionArg
179
179
  * `''` means the meta position (the node you get when you pin nothing).
180
180
  */
181
181
  export declare function entryPositionKeyOf(spec: Pick<AdapterSpec, 'constructionArgs'>, args: Record<string, string> | undefined): string;
182
- /** One adapter-declared interactive connect action — the declaration half
183
- * of the generalised "+" connect affordance. `kind` names an app-shipped
184
- * client handler; `label` is the suggestion text. The language stays pure:
185
- * it carries the declaration and never interprets `kind`. */
186
- export interface ConnectAction {
187
- kind: string;
188
- label: string;
189
- }
190
- /**
191
- * The one UNIVERSAL listen-config key — valid on every listener regardless of
192
- * adapter, like `dry_run` is the universal construction arg. It is NOT a loop
193
- * detector: it's a declarative 2-WAY-SYNC semantic. When an author builds an
194
- * A↔B sync (a movement that both reads and writes the same system) they set
195
- * `suppress_self: true` to mean "ignore my own writes echoing back into this
196
- * system," so the sync doesn't self-trigger.
197
- *
198
- * DEFAULT OFF (absent / false). Off matters: a user legitimately wants to react
199
- * to Listen-Fire's own writes (Movement 1 writes a company to Attio → Movement 2
200
- * posts new Attio companies to Slack). A default-on suppression would nuke that.
201
- *
202
- * Boolean-literal only. Validated centrally in `checkListen` (so every adapter
203
- * accepts it without each declaring it), then carried into
204
- * the persisted trigger config; the dispatch boundary capability-gates it on
205
- * the adapter answering "did WE author this change?". An adapter that can't
206
- * answer makes it a clearly-logged no-op.
207
- */
208
- export declare const SUPPRESS_SELF_KEY = "suppress_self";
209
- /**
210
- * The event node's change-kind field — the ONE protocol name (like
211
- * `suppress_self` or the `events:` listen key) for "which kind of event this
212
- * occurrence is". An adapter with a change-kind axis declares an ordinary
213
- * enum field of this name on its event node, its options exactly the
214
- * listen's `events:` vocabulary; a listen's selection then pins it in the
215
- * derived event address, and a signature narrows with
216
- * `` WHERE `action` == "record.created" ``. Adapters with no such axis
217
- * (cron's tick, a messaging Message Received) simply declare no such field.
218
- *
219
- */
220
- export declare const EVENT_ACTION_FIELD = "action";
221
- /** The `events:` value that means "the record no longer exists" — the pin
222
- * under which `requiresLiveRecord` edges are dropped from a narrowed event
223
- * position (the one real behaviour the retired delete VARIANT carried,
224
- * keyed on the pin rather than on a synthesized name). The `record.*`
225
- * vocabulary is deliberately uniform across adapters (attio, airtable). */
226
- export declare const RECORD_DELETED_ACTION = "record.deleted";
227
182
  export interface CredentialSpec {
228
183
  /** The adapters this credential can authenticate — `slack(credentials: <attio cred>)`
229
184
  * is a type error unless 'slack' is in this set. A credential *type* may serve
@@ -335,6 +290,14 @@ export interface PluginSpec {
335
290
  /** The package tag it is a member of (`@lf/web`), matched exactly
336
291
  * (`packageMembers`); absent when it is in none. */
337
292
  package?: string;
293
+ /**
294
+ * The name a program spells it (`fetch_url`), when the catalog's key is not
295
+ * that: a plugin in a package is keyed by package AND name (`pluginKey`), so
296
+ * two packages can each serve a `render`. Absent: the key is the name.
297
+ */
298
+ name?: string;
299
+ /** What it does, in its author's words (its signature's `description`). */
300
+ description?: string;
338
301
  }
339
302
  /** One `fedArgs` entry: the argument's name, and whether a PLAIN call must
340
303
  * write it (a stage never does either way). */
@@ -342,88 +305,24 @@ export interface PluginFedArg {
342
305
  name: string;
343
306
  required?: true;
344
307
  }
345
- /** One plugin of a package: the name it is called by after the package's
346
- * local name and a dot, and the catalog's key it invokes. */
347
- export interface PackageMember {
348
- name: string;
349
- key: string;
350
- }
351
- /** The plugins whose spec declares `tag` as its package, in catalog order;
352
- * none when no spec does, which is how a package import is told from an
353
- * import of one plugin. */
354
- export declare function packageMembers(catalog: Pick<Catalog, 'plugin' | 'pluginNames'>, tag: string): PackageMember[];
355
- /** The local name a package's member is bound to: `web.fetchUrl`. */
356
- export declare function memberLocal(packageLocal: string, member: string): string;
357
- /** The program with each package import read as the imports of its members
358
- * (`` `@lf/web` as web `` → `fetch_url as web.fetchUrl`, …), which is what an
359
- * engine binds. An import that names no package is left as written. The
360
- * checker does not read this: it binds the same names itself, keeping the
361
- * package to name in a diagnostic. */
362
- export declare function expandPackageImports(program: Program, catalog: Pick<Catalog, 'plugin' | 'pluginNames'>, languageVersion: LanguageVersion): Program;
363
- /** The parameter a plain call hands a plugin fed only by an extraction (from
364
- * language version 5): the record it runs on, which a `through [ … ]` stage
365
- * fed it implicitly. */
366
- export declare const EXTRACTED_RECORD_PARAM = "record";
367
- /**
368
- * A plugin as a plain call sees it under `languageVersion`. From version 5 a
369
- * plugin is a function, so one fed only by an extraction is no longer a
370
- * stage: what the stage fed it implicitly — the record it enriched — becomes
371
- * its one required input, written by position like any fed argument and
372
- * taking structured data. Versions 1–4 read the spec as it is, and refuse the
373
- * call. The Rust twin is `PluginSpec::under` (crates/movement-parser/src/catalog/plugin_args.rs).
374
- */
375
- export declare function pluginSpecUnder(spec: PluginSpec, languageVersion: LanguageVersion): PluginSpec;
376
- /** Where a plugin is called: a plain call writes the arguments a stage is
377
- * fed, a `through [ … ]` stage never does. */
378
- export type PluginCallSite = 'plain' | 'stage';
379
- /** A positional call that does not fit the plugin's parameters. */
380
- export type PluginArgMisfit =
381
- /** An argument past the positional parameters that is not the settings record. */
382
- {
383
- kind: 'extra';
384
- span: Span;
385
- }
386
- /** A settings key that names a parameter taken by position. */
387
- | {
388
- kind: 'settingIsPositional';
389
- name: string;
390
- span: Span;
391
- }
392
- /** A settings record member that is not `name: value`. */
393
- | {
394
- kind: 'settingNotNamed';
395
- span: Span;
396
- };
397
- /** A call's arguments, bound to the plugin's parameters by name. */
398
- export interface PluginArgBinding {
399
- args: CallArg[];
400
- misfits: PluginArgMisfit[];
401
- }
402
- /** The parameters a call fills by position, in the manifest's declared
403
- * order. The spec keeps the fed parameters apart from the author's, and
404
- * every plugin declares its fed one first, so fed-then-author is that order. */
405
- export declare function pluginPositionalParams(spec: PluginSpec, site: PluginCallSite): string[];
406
- /** What is wrong, in the words both checkers report (MOV_THROUGH_BAD_ARG). */
407
- export declare function pluginArgMisfitMessage(misfit: PluginArgMisfit, callee: string, positional: readonly string[]): string;
408
- /** Positional arguments named by `pluginPositionalParams`; the argument
409
- * after those, written as a record literal, is the settings, each entry a
410
- * named argument whose value is its own slot; any further one is a misfit.
411
- * A named argument stays as written. */
412
- export declare function bindPluginArgs(spec: PluginSpec, site: PluginCallSite, args: readonly CallArg[], options: {
413
- languageVersion: LanguageVersion;
414
- }): PluginArgBinding;
415
- /** A `through [ … ]` stage's arguments, bound to the plugin's parameters by name. */
416
- export interface StageArgBinding {
417
- args: NamedArg[];
418
- misfits: PluginArgMisfit[];
419
- }
420
- /** A stage's positional arguments bound as a call's are, with the stage's
421
- * positional parameters (never a fed one). */
422
- export declare function bindStageArgs(spec: PluginSpec, stage: PluginCall, options: {
423
- languageVersion: LanguageVersion;
424
- }): StageArgBinding;
425
- /** Where an argument is written, whatever form it takes. */
426
- export declare function callArgSpan(arg: CallArg): Span;
308
+ /** The catalog key of a package's plugin: module and name together, so two
309
+ * packages can each serve a plugin of the same name. Compared, never parsed
310
+ * (a spec carries its own `name`). The Rust twin is `plugin_key`. */
311
+ export declare function pluginKey(packageTag: string, name: string): string;
312
+ /** A plugin as a program writes it: its key's own name unless the spec says. */
313
+ export declare function pluginProgramNameOf(key: string, spec: PluginSpec): string;
314
+ /** What a written plugin name stands for among `plugins`: the entry keyed by
315
+ * exactly that (a package member's key), else the one entry whose name it is
316
+ * (a bare import, as versions 1 to 4 write it); several such are `ambiguous`,
317
+ * listing the packages that serve it. The Rust twin is `find_plugin`. */
318
+ export declare function findPlugin(plugins: Readonly<Record<string, PluginSpec>>, name: string): {
319
+ found: {
320
+ key: string;
321
+ spec: PluginSpec;
322
+ };
323
+ } | {
324
+ ambiguous: string[];
325
+ } | undefined;
427
326
  /**
428
327
  * What a plugin's plain call evaluates to, on whichever PLANE it lives — the
429
328
  * same two planes every other bound value has, and no third type language:
@@ -449,290 +348,6 @@ export type PluginOutput = {
449
348
  kind: 'records';
450
349
  fields: Record<string, SchemaFieldType>;
451
350
  };
452
- /** What a plain call to `spec`'s plugin hands back to a program pinned to
453
- * `languageVersion` — its current output, or an older shape it kept for
454
- * programs pinned before the change (`PluginSpec.earlierOutputs`). */
455
- export declare function pluginOutputUnder(spec: PluginSpec, languageVersion: LanguageVersion): PluginOutput | undefined;
456
- /**
457
- * The value type of a field/property. Deliberately small: enough to catch
458
- * shape mistakes (text into a number field, a string into a file slot)
459
- * without reproducing every adapter's type system.
460
- */
461
- export type FieldType = 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'file'
462
- /**
463
- * A structured value — the DATA top type, TypeScript's `unknown` for the
464
- * data plane. Every data shape is assignable TO it (text, number, boolean,
465
- * date/datetime, a list of data, an object literal, json itself); `file` is
466
- * not, because a file is a HANDLE, not data. Nothing is assignable FROM it:
467
- * a json value has no known shape, so operating on one (arithmetic,
468
- * comparison, text concatenation) is an error and the author passes it
469
- * through unchanged. Narrowing constructs are deliberately absent — an
470
- * adapter field declared `json` mirrors the target API verbatim, which is
471
- * the point (plans/slack-blocks-json-fields-2026-07-30).
472
- */
473
- | 'json'
474
- /**
475
- * The type of the `null` LITERAL, and of nothing else — TypeScript's `null`
476
- * under strictNullChecks. It is the one value every `T | absent` may turn out
477
- * to be, which is what makes `x == null` a legal comparison at any `T` and a
478
- * meaningful one only where absence is possible. No adapter surface declares
479
- * it (a field is `T` or `maybeAbsent(T)`, never this), so it reaches nothing
480
- * outside the checker's own expression typing.
481
- */
482
- | 'absent'
483
- /**
484
- * The type of a value that never arrives — `fail("…")` written inside an
485
- * expression (language version 5) — TypeScript's `never`. It fits every
486
- * type, and a union with it is the union's other members.
487
- */
488
- | 'never'
489
- /**
490
- * A collection of values. `unordered` is the ORDER discipline's value-plane
491
- * half: the list's members are there, but the sequence they are in means
492
- * nothing, so an order-sensitive fold over it is refused. A list is a
493
- * sequence by construction (a literal, a source's own multi-value field, a
494
- * tuple), so the flag is the exception, set only where the order genuinely
495
- * came from nowhere — a block bound over an unordered traversal, a `COLLECT`
496
- * of one. The position plane carries the same fact differently (a walk's
497
- * last hop is sequenced, or it isn't), exactly as absence does.
498
- */
499
- | {
500
- kind: 'list';
501
- of: FieldType;
502
- unordered?: true;
503
- }
504
- /**
505
- * `<[T, U]>` — a heterogeneous list whose slots were written down: a list
506
- * literal (`[m.Body, file]` is `[text, file]`, from language version 3;
507
- * earlier versions type a literal as the list it reads as) and the receipt of a
508
- * combinator written with literal arms (`await parallel([f, g])`). Every slot
509
- * is its own type, so a literal-index read (`AT(r, 0)`) types exactly; every
510
- * other operation reads a tuple as the list it widens to — TypeScript's
511
- * tuple-to-array assignability.
512
- *
513
- * A slot is `null` where the value could not be typed — "we could not see",
514
- * spelled with the one nothing-value JSON can carry, since this type crosses
515
- * the wire verbatim. `'absent'` is the other answer and means the opposite:
516
- * this arm hands nothing back, so the slot's VALUE is always null at run
517
- * time.
518
- *
519
- * `rest` makes the tuple VARIADIC — TypeScript's `[T, ...U[]]`: a run of
520
- * any length (zero included) of `rest.of`, sitting just before slot
521
- * `rest.at` (`at === of.length` puts it last). A spread of a list into a
522
- * literal is what writes one: `[m.Body, ...files]` is `[text, ...file[]]`.
523
- * There is at most one run — a second spread folds everything from the
524
- * first run onward into it, as TypeScript does.
525
- */
526
- | {
527
- kind: 'tuple';
528
- of: Array<FieldType | null>;
529
- rest?: TupleRest;
530
- }
531
- /**
532
- * `{ k: v, … }` — a collection keyed by TEXT. Keys are strings and nothing
533
- * else: a dict is JSON-object-shaped, which is the whole value world's one
534
- * story (snapshots, `json` interop, the wire), and JSON has no other key. A
535
- * key that arrives as some other type is a save error demanding the author
536
- * write the coercion down, never a silent stringification.
537
- *
538
- * `of` is what a key nobody wrote down may hold: the value type every key
539
- * shares, or `json` when they disagree — json is the data type every value
540
- * flows into. (A value union exists, but only as what a tuple widens to.)
541
- *
542
- * `shape` is the TypeScript object literal's type: a dict WRITTEN as a
543
- * literal knows its keys, so a lookup by a literal key reads that key's own
544
- * type, present, and a literal key it does not have is refused. A key is
545
- * `null` where its value could not be typed — "we could not see", as a
546
- * tuple's slot is. Absent on every dict whose keys are data rather than
547
- * program text: `GROUPBY`/`KEYBY`'s answers, and every adapter surface.
548
- *
549
- * There is no order: a dict is looked up, never folded, so the set/list
550
- * discipline has nothing to say about one. What `GROUPBY` puts INSIDE it —
551
- * a list per key — carries the source's ordering as any other list does.
552
- */
553
- | {
554
- kind: 'dict';
555
- of: FieldType;
556
- shape?: Record<string, FieldType | null>;
557
- }
558
- /** `open` marks a KNOWN-VALUES field, not a closed enum: the options are
559
- * what the adapter could enumerate live (Slack channels, select options),
560
- * but other values remain legal — ids matching `allowPattern`, runtime
561
- * expressions, values the listing couldn't see. Literal membership misses
562
- * are WARNINGS with a did-you-mean, never errors. */
563
- | {
564
- kind: 'enum';
565
- options: string[];
566
- open?: {
567
- allowPattern?: string;
568
- };
569
- }
570
- /**
571
- * `T | absent` — a value that MAY not be there (asks-as-adapter P20/F13). Read
572
- * from a PARTIAL context (a race receipt property bound in only some branches,
573
- * F18) or a maybe-empty node's field (an awaited landing off a `resolvesEmpty`
574
- * edge, F19/F20). It PROPAGATES silently through expressions; the checker
575
- * errors only where a NON-ABSENT value is REQUIRED (a plain write field, an
576
- * ordered comparison), never at the read — the TS `T | undefined` model.
577
- * Discharged by a `?:` (fill) write field, a traversal-as-gate, or an `==`
578
- * guard (equality tolerates absence and narrows). Produced ONLY by
579
- * the checker's typing layer; it never appears in an adapter/schema surface, so
580
- * runtime data (the engine, trpc) never carries it. `of` is never itself
581
- * `maybeAbsent` — construct via the `maybeAbsent()` helper, which flattens. */
582
- | {
583
- kind: 'maybeAbsent';
584
- of: FieldType;
585
- }
586
- /**
587
- * `T | U` — a value that is one of several types, TypeScript's union. It
588
- * arises where a tuple is READ as a list: `[text, number]` widens to a list
589
- * of `text | number`, as TypeScript widens `[string, number]` to
590
- * `(string | number)[]` (language version 3; earlier versions read such a
591
- * tuple as a list nobody can type).
592
- *
593
- * A union IS its member set, as a record-position union is: build one only
594
- * through `valueUnion`, which keeps the set canonical — flattened (no member
595
- * is a union), de-duplicated, sorted by display, at least two members (one
596
- * member is that member), absence hoisted out (`maybeAbsent` wraps the
597
- * union, never sits inside it), a member another member already accepts
598
- * absorbed (`json` takes every data shape, `text` every enum). Members are
599
- * VALUES: records and values have no union (a list of both is
600
- * `MOV_LIST_MIXED`), and records of different positions are already one
601
- * record type.
602
- *
603
- * Produced only by the checker's typing layer; no adapter surface declares
604
- * one.
605
- */
606
- | {
607
- kind: 'union';
608
- of: FieldType[];
609
- }
610
- /**
611
- * A RECORD held as a value — a traversed record, a write handle, a
612
- * synthesised node, an extraction result. One type universe: a record is
613
- * what `MAP` hands back when its function returns one, what a list literal
614
- * of them holds, and what a map's key can carry, so lists and dicts nest
615
- * over records exactly as they nest over text.
616
- *
617
- * It is a value TYPE, not a value shape: nothing reads it as data. A record
618
- * written into a field, interpolated into text, added up or compared to a
619
- * scalar is refused where it is written; what it CAN do is be walked from
620
- * (a block head), read by field (the dot plane), and held in a collection.
621
- *
622
- * `position` is which record, where the checker can say — the same
623
- * `PositionTypeRef` the arrow plane uses, never a parallel one. ABSENT means
624
- * "a record, and nobody can name which": members of a list that disagree,
625
- * an extraction result, a synthesised node. The distinction is load-bearing
626
- * and is the same one `LocalEdge.target` draws — a walk off a record whose
627
- * position is unknown stays silent and runs, and one off a named position is
628
- * checked.
629
- */
630
- | {
631
- kind: 'record';
632
- position?: PositionTypeRef;
633
- };
634
- /** A variadic tuple's run: any number of `of`, just before slot `at`. `of` is
635
- * `null` where the run's members could not be typed. */
636
- export interface TupleRest {
637
- at: number;
638
- of: FieldType | null;
639
- }
640
- /**
641
- * What an adapter SURFACE can declare — every field type except a record,
642
- * nested collections included.
643
- *
644
- * A record is a place in a graph, and no adapter field is one: a record type
645
- * arises only where the checker types an EXPRESSION. Saying so here is what
646
- * keeps a schema clear of the position model — and a schema crosses the wire
647
- * (`InstanceSchema` is a tRPC response), where a field type that reached into
648
- * positions would reach back into schemas again, a cycle no serialiser's type
649
- * can follow.
650
- *
651
- * The collections are spelled a second time because TypeScript cannot subtract
652
- * a member from a RECURSIVE union — `Exclude` removes the record at the top
653
- * and leaves `list of record` behind, which is the same cycle. The scalars and
654
- * every rule about them live on `FieldType` above; this is that union with one
655
- * member gone, and every `SchemaFieldType` IS a `FieldType`, so nothing that
656
- * reads a schema needs to know which it was handed.
657
- */
658
- export type SchemaFieldType = Exclude<FieldType, {
659
- kind: string;
660
- }> | {
661
- kind: 'list';
662
- of: SchemaFieldType;
663
- unordered?: true;
664
- } | {
665
- kind: 'tuple';
666
- of: Array<SchemaFieldType | null>;
667
- } | {
668
- kind: 'dict';
669
- of: SchemaFieldType;
670
- } | {
671
- kind: 'enum';
672
- options: string[];
673
- open?: {
674
- allowPattern?: string;
675
- };
676
- } | {
677
- kind: 'maybeAbsent';
678
- of: SchemaFieldType;
679
- };
680
- /**
681
- * A field type spelled as ONE tagged variant — the scalars given the `kind`
682
- * the collections already carry.
683
- *
684
- * `FieldType` is half bare names and half objects, so a `switch` could never
685
- * see it whole: every branch over it was a `typeof === 'object'` if-chain, the
686
- * compiler enumerated nothing, and a new variant silently took whichever
687
- * fallthrough happened to come last. Both halves here are DERIVED from the
688
- * type parameter, so this is not a second list to keep in step — add a variant
689
- * to `FieldType` and every `switch` over a variant stops compiling at once.
690
- *
691
- * Generic in the type it normalises so a `SchemaFieldType` stays one: its
692
- * `list` still holds a `SchemaFieldType`, and its switch still has no `record`
693
- * case to answer.
694
- */
695
- export type FieldTypeVariant<T extends FieldType = FieldType> = {
696
- kind: Extract<T, string>;
697
- } | Extract<T, {
698
- kind: string;
699
- }>;
700
- /**
701
- * Normalise a field type for an exhaustive `switch (variantOf(t).kind)`.
702
- *
703
- * The one assertion is the boundary the whole scheme rests on: `typeof` cannot
704
- * prove to TypeScript that a GENERIC `T`'s string half is `Extract<T, string>`,
705
- * though it is, by construction of the union.
706
- */
707
- export declare function variantOf<T extends FieldType>(type: T): FieldTypeVariant<T>;
708
- /** Maps a surface type name (shape declarations, extract annotations) to a SchemaFieldType. */
709
- /**
710
- * The type a `type Thesis = <"A" | "B">` declaration names — a CLOSED enum,
711
- * identical in shape to an option set borrowed from a live field, so nothing
712
- * downstream (extraction prompt, literal check, did-you-mean) can tell a
713
- * written refinement from a fetched one.
714
- */
715
- export declare function declaredTypeOf(decl: TypeDeclaration): SchemaFieldType;
716
- /**
717
- * Every refinement a program declares, by name. A `type` is a FILE-level
718
- * declaration, so this flat map and the checker's scope resolution answer the
719
- * same question — the engine, which has no scopes, asks it this way.
720
- */
721
- export declare function declaredTypesIn(program: Program): Map<string, SchemaFieldType>;
722
- export declare function parseFieldTypeName(name: string | undefined): SchemaFieldType | undefined;
723
- /** The dotted segments of a borrowed-type annotation, or undefined for a
724
- * plain (primitive) type name. */
725
- export declare function borrowedTypeSegments(name: string | undefined): string[] | undefined;
726
- /**
727
- * The borrowable fields of one root/position of a schema: the writable
728
- * root's fields unioned with the position's properties. Writable fields
729
- * win per-name — enums (select options) live on the write surface.
730
- * Undefined when the schema has no root or position of that name.
731
- */
732
- export declare function borrowableFieldsOf(schema: InstanceSchema, rootName: string): Record<string, SchemaFieldType> | undefined;
733
- /** Resolve `<root>.<field>` of a borrowed path against a schema. */
734
- export declare function resolveBorrowedField(schema: InstanceSchema, rootName: string, fieldName: string): SchemaFieldType | undefined;
735
- export declare function describeFieldType(type: FieldType): string;
736
351
  /** A reference (edge) declared by a position type. */
737
352
  export interface EdgeSchema {
738
353
  /** The position type (or union) the edge points at. */
@@ -909,6 +524,9 @@ export interface EdgeSchema {
909
524
  * only: nothing resolves `_resources` to it.
910
525
  */
911
526
  resources?: true;
527
+ /** What the adapter says the edge is (a reference's `description`). The
528
+ * checker never reads it; the language service's hover leads with it. */
529
+ description?: string;
912
530
  }
913
531
  /** A readable position type: its properties and outgoing references. */
914
532
  export interface PositionSchema {
@@ -1049,22 +667,14 @@ export interface PositionSchema {
1049
667
  * undescribed or pending position says nothing, so nothing is refused).
1050
668
  */
1051
669
  deletable?: boolean;
670
+ /** What the adapter says a record of this type is, in its own words (its
671
+ * describe's or entry point's `description`). The checker never reads it;
672
+ * the language service's hover leads with it. */
673
+ description?: string;
674
+ /** What the adapter says each field is, by name — every field it described,
675
+ * readable or write-only. */
676
+ fieldDocs?: Record<string, string>;
1052
677
  }
1053
- /**
1054
- * May a check state that this position's surface LACKS something?
1055
- *
1056
- * No, on any of the three facts above: an OPEN position says its surface is
1057
- * wider than what's enumerated, an UNDESCRIBED one says nothing at all, and a
1058
- * PENDING one has not been asked yet — none licenses "it has no field X".
1059
- *
1060
- * They stay separate fields because they answer a DIFFERENT question
1061
- * differently: may the author touch the handle? An open position, yes (it is a
1062
- * positive claim); an undescribed one, no (`MOV_UNDESCRIBED_POSITION`); a
1063
- * pending one, yes-for-now — silently, because the answer is still coming and
1064
- * the author has done nothing wrong. This helper is only for the first
1065
- * question, and exists so no call site has to remember to ask about all three.
1066
- */
1067
- export declare function surfaceNotEnumerated(schema: PositionSchema | undefined): boolean;
1068
678
  /**
1069
679
  * A DISCRIMINATED write shape — the write-side dual of read narrowing. On the
1070
680
  * read side a WHERE's literal selects a variant type (`-[:Base WHERE `Name` ==
@@ -1428,25 +1038,6 @@ export interface InstanceSchema {
1428
1038
  */
1429
1039
  eventNarrowingValues?: Record<string, Record<string, string[]>>;
1430
1040
  }
1431
- /** Whether a write may land on this edge. The two promises default OPPOSITE
1432
- * ways — `readable` absent means readable, `writable` absent means read-only
1433
- * (layer 13) — which is exactly the asymmetry a second reader gets wrong. So
1434
- * everyone who asks asks HERE: the checker's gate, the diagnostics that list
1435
- * the writable spellings, and the agent-facing describe surface. They cannot
1436
- * answer differently if there is only one answer. */
1437
- export declare function edgeIsWritable(edge: EdgeSchema): boolean;
1438
- /** Whether a read traversal may walk this edge — the other half of the pair
1439
- * above, and the one that defaults TRUE (only `readable: false` withdraws it,
1440
- * marking a write-only edge). */
1441
- export declare function edgeIsReadable(edge: EdgeSchema): boolean;
1442
- /** Every WRITABLE edge an instance offers, for diagnostics: the spellings a
1443
- * write can land on (`channel-[:messages]->`, `message-[:replies]->`). Keys
1444
- * off the one explicit `writable` promise (layer 13). */
1445
- export declare function writableEdgesOf(schema: InstanceSchema): Array<{
1446
- parent: string;
1447
- edge: string;
1448
- target: string;
1449
- }>;
1450
1041
  /**
1451
1042
  * Canonical key into `InstanceSchema.refinements`: the hop's target type plus
1452
1043
  * the WHERE that selects the position. Shared by the checker (looking
@@ -1559,3 +1150,94 @@ export declare function mockCatalog(spec: {
1559
1150
  constructionArgOptions?: Catalog['constructionArgOptions'];
1560
1151
  models?: readonly string[];
1561
1152
  }): Catalog;
1153
+ /**
1154
+ * A row a DECLARATION states, for a function whose body the checker cannot
1155
+ * walk — a plugin, an adapter's field function, a remote callee. The inferred
1156
+ * row's twin: same five facts, written down instead of derived, and folded into
1157
+ * a caller by exactly the same `absorb`.
1158
+ *
1159
+ * `reads`/`writes` name synthetic sources in the words an author would read
1160
+ * ("the web"). They are not instances: a declaration is written once, in the
1161
+ * registry, and cannot know which graphs the movement that calls it constructed.
1162
+ *
1163
+ * There is no `partial` here. A declaration is a CLAIM — it says what the
1164
+ * function does, completely. Not declaring one at all is the lower bound, and
1165
+ * that is the absence of this object, not a field inside it.
1166
+ */
1167
+ export interface DeclaredEffectRow {
1168
+ readonly reads?: readonly string[];
1169
+ readonly writes?: readonly string[];
1170
+ readonly ai?: boolean;
1171
+ readonly now?: boolean;
1172
+ readonly suspend?: boolean;
1173
+ }
1174
+ /** The prefix that marks an edge the ENGINE makes rather than one a system
1175
+ * declares. Reserved in every version: the host leaves an adapter edge so
1176
+ * named out of its projection, with a note, so the two can never collide. */
1177
+ export declare const ARTIFICIAL_EDGE_PREFIX = "@";
1178
+ export declare function isArtificialEdge(edge: string): boolean;
1179
+ /**
1180
+ * The EVENT NAME a key was minted from — the inverse of `eventAddressKey`'s
1181
+ * event half, and it lives HERE so the key stays opaque to everyone else. Its
1182
+ * consumers only ever compare it; the moment one of them pulled the name out
1183
+ * itself, the key would be a magic string that is PARSED rather than compared.
1184
+ *
1185
+ * This exists because the engine stamps an event position's `recordType` with
1186
+ * the key, and any name resolution scoped by type then has to ask which type
1187
+ * that is. An UNPINNED key already IS the event name, which is why adapters
1188
+ * whose events pin nothing (Granola, an unnarrowed Attio event) resolve
1189
+ * correctly by COINCIDENCE — and why Airtable, which always pins base and
1190
+ * table, never did.
1191
+ *
1192
+ * A string this module did not mint is returned unchanged: it is an ordinary
1193
+ * type name, and saying so is the honest answer.
1194
+ */
1195
+ export declare function eventAddressEventName(key: string): string;
1196
+ /**
1197
+ * What an adapter SURFACE can declare — every field type except a record,
1198
+ * nested collections included.
1199
+ *
1200
+ * A record is a place in a graph, and no adapter field is one: a record type
1201
+ * arises only where the checker types an EXPRESSION. Saying so here is what
1202
+ * keeps a schema clear of the position model — and a schema crosses the wire
1203
+ * (`InstanceSchema` is a tRPC response), where a field type that reached into
1204
+ * positions would reach back into schemas again, a cycle no serialiser's type
1205
+ * can follow.
1206
+ *
1207
+ * The collections are spelled a second time because TypeScript cannot subtract
1208
+ * a member from a RECURSIVE union — `Exclude` removes the record at the top
1209
+ * and leaves `list of record` behind, which is the same cycle. The scalars and
1210
+ * every rule about them live on `FieldType` above; this is that union with one
1211
+ * member gone, and every `SchemaFieldType` IS a `FieldType`, so nothing that
1212
+ * reads a schema needs to know which it was handed.
1213
+ */
1214
+ export type SchemaFieldType = 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'file' | 'json' | 'absent' | 'never' | {
1215
+ kind: 'list';
1216
+ of: SchemaFieldType;
1217
+ unordered?: true;
1218
+ } | {
1219
+ kind: 'tuple';
1220
+ of: Array<SchemaFieldType | null>;
1221
+ } | {
1222
+ kind: 'dict';
1223
+ of: SchemaFieldType;
1224
+ } | {
1225
+ kind: 'enum';
1226
+ options: string[];
1227
+ open?: {
1228
+ allowPattern?: string;
1229
+ };
1230
+ } | {
1231
+ kind: 'maybeAbsent';
1232
+ of: SchemaFieldType;
1233
+ };
1234
+ /** `T | absent` over a surface type; a surface type in is a surface type out. */
1235
+ export declare function maybeAbsent(type: SchemaFieldType | undefined): SchemaFieldType | undefined;
1236
+ /** One adapter-declared interactive connect action — the declaration half
1237
+ * of the generalised "+" connect affordance. `kind` names an app-shipped
1238
+ * client handler; `label` is the suggestion text. The language stays pure:
1239
+ * it carries the declaration and never interprets `kind`. */
1240
+ export interface ConnectAction {
1241
+ kind: string;
1242
+ label: string;
1243
+ }