@oddington/sdk 0.1.38 → 0.1.39
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/README.md +152 -39
- package/dist/browser.js +413 -106
- package/dist/index.js +9 -4
- package/dist/types/packages/sdk/src/public.d.ts +3 -2
- package/dist/types/shared/data-contract.d.ts +80 -8
- package/dist/types/shared/data-validation-message.d.ts +23 -0
- package/dist/types/shared/files-contract.d.ts +3 -0
- package/dist/types/shared/inbox-contract.d.ts +11 -0
- package/dist/types/shared/routine-ai-contract.d.ts +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@ The importable client for OS apps, routines and response handlers.
|
|
|
4
4
|
Node operations require Node.js 22.12.0 or later. The root import and hosted
|
|
5
5
|
Data and Files APIs work in browsers. Installing the package grants no authority.
|
|
6
6
|
|
|
7
|
+
When developing OS alongside a customer repository, use the repository's
|
|
8
|
+
[`dev:customer` helper](../../README.md) to run Bun tests and TypeScript checks
|
|
9
|
+
against the built local SDK without changing the customer's released pin,
|
|
10
|
+
lockfile or installed package. Build the SDK and CLI together first; the same
|
|
11
|
+
helper can execute that built CLI from the customer checkout.
|
|
12
|
+
|
|
7
13
|
Hosted Data and shared Files reach OS directly through `/api/data` and `/api/files`.
|
|
8
14
|
Inbox owns workspace-hosted `/api/inbox/check`, `/api/inbox/publish`,
|
|
9
15
|
`/api/inbox/resolve`. Request-key recovery and captured
|
|
@@ -88,7 +94,7 @@ this metadata when absent. Setup and safe inspection are documented in the
|
|
|
88
94
|
|
|
89
95
|
With `access: { ai: true }`, `os.ai.generateText` returns non-empty `{ text }`.
|
|
90
96
|
`os.ai.generateObject` returns `{ object }` validated against the requested
|
|
91
|
-
`schema`, with its inferred type. An invalid answer throws `
|
|
97
|
+
`schema`, with its inferred type. An invalid answer throws `AIError`
|
|
92
98
|
with code `invalid_output`, a clear bounded `detail`, and the original final
|
|
93
99
|
answer as optional `text` for routine-owned recovery. The text is bounded at
|
|
94
100
|
256 KiB and excluded from the error message and ordinary JSON serialization.
|
|
@@ -96,6 +102,29 @@ The routine can inspect it explicitly, apply its own recovery rules, report
|
|
|
96
102
|
an item failure and continue; it must not cast a rejected answer to the promised
|
|
97
103
|
type. OS does not coerce, drop fields, repair or retry automatically.
|
|
98
104
|
|
|
105
|
+
`generateObject` also accepts one exact outer Markdown code fence (empty or
|
|
106
|
+
`json` language tag), then applies the same strict schema checks. It does not
|
|
107
|
+
extract JSON from explanatory prose or repair the answer. `RoutineAIError`
|
|
108
|
+
remains a compatibility alias of `AIError`, so existing `instanceof` checks work.
|
|
109
|
+
|
|
110
|
+
Preserve safe AI error facts when reporting a per-document failure. Keep the
|
|
111
|
+
original answer (`error.text`) out of step data, business records and logs:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { AIError } from '@oddington/sdk';
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const { object } = await os.ai.generateObject({ input, instructions, schema });
|
|
118
|
+
// Apply the routine's business validation before using the result.
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (!(error instanceof AIError)) throw error;
|
|
121
|
+
return { status: 'unavailable', error: {
|
|
122
|
+
code: error.code, message: error.message,
|
|
123
|
+
...(error.detail ? { detail: error.detail } : {}),
|
|
124
|
+
} };
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
99
128
|
An AI call failure can be caught and handled without automatically failing the
|
|
100
129
|
Run. Uncaught routine or step exceptions still fail execution, and outstanding
|
|
101
130
|
AI calls drain before completion. The host retains authority, isolation,
|
|
@@ -107,11 +136,13 @@ finite numbers and safe integers with optional inclusive bounds, and Files.
|
|
|
107
136
|
Omitting `maxItems` imposes no item-count ceiling; the complete serialized input
|
|
108
137
|
still shares the Run's byte bound, including every File descriptor and other field.
|
|
109
138
|
Definitions allow at most 64 steps, 64 total object properties across their schemas,
|
|
110
|
-
and eight schema levels.
|
|
111
|
-
|
|
139
|
+
and eight schema levels. Run input, each summary, each detail value, and the result
|
|
140
|
+
accept at most 256 KiB of UTF-8 serialized JSON, without truncation. Step inputs
|
|
141
|
+
remain closed, immutable JSON but have no separate byte ceiling because they are
|
|
142
|
+
transient in-process values rather than transported or retained Run evidence.
|
|
112
143
|
Across 64 steps, retained summary plus detail is at most 32 MiB, plus 512 KiB for
|
|
113
|
-
Run input and result
|
|
114
|
-
|
|
144
|
+
Run input and result. Schema defaults share the input bound. Titles contain at
|
|
145
|
+
most 256 UTF-16 code units;
|
|
115
146
|
access dimensions and response handlers each have at most 64 entries. Functions,
|
|
116
147
|
accessors, sparse arrays, cycles and non-JSON values reject. The canonical routine
|
|
117
148
|
ID is a bounded lowercase alphanumeric/hyphen name, independent of its directory.
|
|
@@ -345,13 +376,18 @@ both shipped SDK inputs and every emitted helper, rejects unexpected inputs and
|
|
|
345
376
|
imports, and retains the full credential/server/customer leak scan. Node retains
|
|
346
377
|
the complete package-root exports; provider classes are never public.
|
|
347
378
|
|
|
348
|
-
Current auth measurement is **
|
|
349
|
-
bytes** and **
|
|
350
|
-
Data/Files fixture measures **
|
|
351
|
-
bytes**, with **
|
|
352
|
-
the local Questions transport
|
|
353
|
-
|
|
354
|
-
|
|
379
|
+
Current auth measurement is **186,220 minified bytes**, including **8,189 fixture
|
|
380
|
+
bytes** and **178,031 SDK bytes**, with **50,478 total gzip bytes**. Current
|
|
381
|
+
Data/Files fixture measures **58,294 total / 7,204 fixture / 51,090 SDK
|
|
382
|
+
bytes**, with **18,751 total gzip bytes**. The shipped runtime also carries
|
|
383
|
+
the local Questions transport, transparent Local Host renewal, and bounded
|
|
384
|
+
Data validation details and guidance.
|
|
385
|
+
Both fit their explicitly reviewed ceilings: **64,000 SDK bytes** for the pinned
|
|
386
|
+
Data/Files fixture and **256,000 SDK bytes** for the separately pinned auth fixture.
|
|
387
|
+
The combined runtime includes validation guidance and local Data recovery
|
|
388
|
+
without another dependency. These allowances leave room for ordinary feature growth while
|
|
389
|
+
catching substantial size regressions. Dependency, compatibility and private-code
|
|
390
|
+
audits remain separate checks.
|
|
355
391
|
Those ceilings are fixed review decisions, not values regenerated from later
|
|
356
392
|
measurements. The installed auth realm/host-handoff proof passes alongside the
|
|
357
393
|
Node exports, declarations, tarball leak scan and browser compatibility audits.
|
|
@@ -365,17 +401,18 @@ bytes; generated helpers count toward the SDK. Gzip is diagnostic only.
|
|
|
365
401
|
|
|
366
402
|
| Same named-import fixture | Legacy diagnostic | Current strict proof |
|
|
367
403
|
| --- | ---: | ---: |
|
|
368
|
-
| Total minified JavaScript bytes | 488,539 |
|
|
369
|
-
| Fixture-attributed bytes | 5,954 |
|
|
370
|
-
| SDK bytes | 482,585 |
|
|
371
|
-
| Total gzip bytes | 198,612 |
|
|
404
|
+
| Total minified JavaScript bytes | 488,539 | 58,294 |
|
|
405
|
+
| Fixture-attributed bytes | 5,954 | 7,204 |
|
|
406
|
+
| SDK bytes | 482,585 | 51,090 |
|
|
407
|
+
| Total gzip bytes | 198,612 | 18,751 |
|
|
372
408
|
|
|
373
409
|
The legacy diagnostic externalized three unresolved Node built-ins and failed
|
|
374
410
|
browser compatibility. The current strict proof has no imports, shims or warnings
|
|
375
|
-
and reduces SDK bytes by
|
|
376
|
-
|
|
411
|
+
and reduces SDK bytes by 89.4%. Its literal ceiling is **64,000 SDK bytes**.
|
|
412
|
+
The measurement includes
|
|
377
413
|
the Local Host bootstrap, local Questions transport, transparent renewal and
|
|
378
|
-
opaque browser-auth handoff that `os.init` must select at runtime
|
|
414
|
+
opaque browser-auth handoff that `os.init` must select at runtime, and bounded
|
|
415
|
+
Data validation errors. The verifier pins the fixture
|
|
379
416
|
hash and bundler version; changing either requires a reviewed measurement
|
|
380
417
|
comparison. Future ceiling increases require explicit review of added browser
|
|
381
418
|
capability and must never be automatically regenerated from an oversized output.
|
|
@@ -424,6 +461,7 @@ Blob and byte-array declarations must match their intrinsic size. Originals are
|
|
|
424
461
|
bounded to 5 GiB per original and 16 MiB per enrichment. Large originals stream
|
|
425
462
|
through bounded multipart transfers; local writes and reads use bounded disk
|
|
426
463
|
buffers. Operations share four concurrent slots and idle/request deadlines;
|
|
464
|
+
later calls wait in invocation order without reading caller content, and
|
|
427
465
|
transport retries preserve the same bytes and request key.
|
|
428
466
|
Omitting a key generates one for that invocation. Supply a stable key to recover
|
|
429
467
|
across processes or fresh routine runs. Caller inputs remain owned by the caller.
|
|
@@ -473,7 +511,7 @@ const changes = data.repository('general').changes();
|
|
|
473
511
|
changes.create({ ...statement, added: '2026-10-20' });
|
|
474
512
|
changes.create({ $ref: 'transactions/one',
|
|
475
513
|
sources: [source.csv.row(statement.$ref, parsedTransaction.recordPosition)] });
|
|
476
|
-
await changes.
|
|
514
|
+
await changes.apply();
|
|
477
515
|
```
|
|
478
516
|
|
|
479
517
|
The caller's CSV parser supplies `recordPosition` from the retained statement:
|
|
@@ -490,7 +528,12 @@ guarded Data update. Known objects match by OS File ID, Gmail connection/message
|
|
|
490
528
|
attachment, or Dropbox connection/File ID; newer Locations replace old path,
|
|
491
529
|
revision and URL facts. Distinct objects and custom provider values remain.
|
|
492
530
|
Helpers need no initialization and perform no I/O, choose no ref, and add no
|
|
493
|
-
changes. File retention and Data validation/
|
|
531
|
+
changes. File retention and Data validation/application remain explicit.
|
|
532
|
+
|
|
533
|
+
File Sources use the OS-defined fields `filename`, `media_type`, `size_bytes`,
|
|
534
|
+
and `sha256`. Keep the customer repository's Source schema aligned with this
|
|
535
|
+
definition when updating it. `mime_type` is not an alias for `media_type`;
|
|
536
|
+
do not rename a field produced by `source.file()` to fit an incompatible schema.
|
|
494
537
|
|
|
495
538
|
Microsoft helpers are also exported at the package root, with the corresponding
|
|
496
539
|
`OutlookSourceLocation` and `SharePointSourceLocation` types:
|
|
@@ -508,7 +551,7 @@ const itemLocation = source.location.sharepoint({
|
|
|
508
551
|
|
|
509
552
|
Construct these only after successful provider reads establish the coordinates
|
|
510
553
|
for the retained bytes, and save them alongside `source.location.os(file)` using
|
|
511
|
-
ordinary Data
|
|
554
|
+
an ordinary Data apply. Request `Prefer: IdType="ImmutableId"` consistently
|
|
512
555
|
when obtaining Outlook message and attachment IDs. These IDs are case-sensitive
|
|
513
556
|
and survive folder moves within the same mailbox, but not archive-mailbox moves
|
|
514
557
|
or export/reimport. Subjects, filenames and `internetMessageId` are not substitutes.
|
|
@@ -613,7 +656,9 @@ use `select` for a manual `after` cursor or explicit historical selection.
|
|
|
613
656
|
`all` consumes the same iterator and returns only after successful exhaustion.
|
|
614
657
|
Its required positive safe-integer `maxRecords` caps top-level Record occurrences
|
|
615
658
|
across all collections; expanded links do not count toward that ceiling. Overflow
|
|
616
|
-
|
|
659
|
+
names the queried collections and ceiling, tells the author to narrow the query
|
|
660
|
+
or increase `maxRecords`, and rejects with nonretryable `capacity` without a
|
|
661
|
+
partial or truncated result.
|
|
617
662
|
Invalid helper arguments use `invalid_query`; malformed pages, cursor cycles,
|
|
618
663
|
snapshot changes or inconsistent requested counts use nonretryable `unavailable`.
|
|
619
664
|
Selection and authority failures propagate unchanged, with no retry or restart.
|
|
@@ -730,38 +775,106 @@ changes.create({
|
|
|
730
775
|
changes.update(first.invoices[0], { amount: 30 });
|
|
731
776
|
changes.delete(first.invoices[1]);
|
|
732
777
|
await changes.validate();
|
|
733
|
-
const
|
|
778
|
+
const applied = await changes.apply();
|
|
734
779
|
```
|
|
735
780
|
|
|
781
|
+
Use reserved `$body` to author a Record's Markdown content independently of its
|
|
782
|
+
schema fields, in the same guarded change:
|
|
783
|
+
|
|
784
|
+
```ts
|
|
785
|
+
changes.create({ $ref: 'emails/email-123', type: 'email', subject: 'Invoice',
|
|
786
|
+
$body: 'Hello,\n\nPlease find our invoice attached.\n' });
|
|
787
|
+
changes.update(existingEmail, { $body: revisedMarkdown }); // body-only change
|
|
788
|
+
const emails = await general.records.read(emailRefs, { body: true });
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
Omitted `$body` on create means empty; omitted on update preserves the existing
|
|
792
|
+
body exactly. A string deliberately replaces it, `''` clears it, and `null` is
|
|
793
|
+
invalid. Body-only edits preserve front matter, field-only edits preserve the
|
|
794
|
+
body, and both share the Record's exact-byte version guard. Bodies round-trip
|
|
795
|
+
without Markdown interpretation, formatting, trimming, or fence semantics.
|
|
796
|
+
Ordinary reads, selections and search stay body-free; explicitly requested
|
|
797
|
+
bodies use the same ordered batch/null-slot shape. Local change batches support
|
|
798
|
+
32 MiB of serialized JSON; hosted requests retain their separate 256 KiB limit.
|
|
799
|
+
Body-enabled read responses are bounded to 8 MiB, with an explicit error
|
|
800
|
+
instead of truncation. Hosted body reads use a protected immutable v3 index;
|
|
801
|
+
older indexes without body coverage are unavailable rather than falsely empty.
|
|
802
|
+
|
|
803
|
+
The customer routine owns content and authoring policy. For imported emails,
|
|
804
|
+
create with the prepared body, omit `$body` on ordinary reruns, and backfill
|
|
805
|
+
only bodies explicitly read as exactly empty. Existing notes and fenced blocks
|
|
806
|
+
remain ordinary authored content. A fresh version alone does not authorize
|
|
807
|
+
overwriting an existing nonempty body.
|
|
808
|
+
|
|
736
809
|
Creation captures a complete Record and stable `$ref`, without I/O or a caller
|
|
737
810
|
path. OS resolves exactly one collection and its schema-declared filing path.
|
|
738
811
|
Optional `$filing` supplies only unresolved named safe components and is never
|
|
739
812
|
stored or returned as Record data; missing components reject by name. Updates
|
|
740
813
|
capture only `$ref`, `$version` and explicit fields. Deletion captures only the
|
|
741
|
-
selected `$ref` and `$version`.
|
|
742
|
-
schema-declared links from any committed Record outside it refuse
|
|
743
|
-
even if a proposed update removes that link.
|
|
744
|
-
candidate graph without effects
|
|
745
|
-
|
|
814
|
+
selected `$ref` and `$version`. For hosted Data, links within the deletion set
|
|
815
|
+
are allowed; schema-declared links from any committed Record outside it refuse
|
|
816
|
+
the whole group, even if a proposed update removes that link. Hosted validation
|
|
817
|
+
checks the complete candidate graph without effects, and `apply()` repeats
|
|
818
|
+
authoritative validation before writing every create/update/delete together.
|
|
746
819
|
Success returns `{ version, created: [{ $ref, $version }], updated: [{ $ref, $version }], deleted: [{ $ref, $version }] }`
|
|
747
820
|
only after its exact index is ready. Later current reads discover that version.
|
|
748
821
|
Deleted identities keep their captured versions; deletion creates no new Record
|
|
749
822
|
version or tombstone. A Source deletion removes only that guarded Record, retaining
|
|
750
823
|
its File, enrichments and existing durable uses.
|
|
751
|
-
During a local Run, `changes.
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
824
|
+
During a local Run, `changes.apply()` validates the touched Records, paths,
|
|
825
|
+
authored links and captured versions without auditing unrelated Record
|
|
826
|
+
contents, then writes individual complete files atomically. Completed paths
|
|
827
|
+
remain after a later filesystem failure. `validate()` grants no later
|
|
828
|
+
permission to write; subsequent operations read the current checkout. There is
|
|
829
|
+
no later Run publication, promotion or prepared repository state.
|
|
755
830
|
|
|
756
831
|
`invalid_change` and known-unapplied `conflict` reject the batch.
|
|
757
|
-
|
|
832
|
+
Unknown, missing, or invalid field failures include the Record ref and
|
|
833
|
+
field name in the SDK error's `message`, with guidance to check the collection
|
|
834
|
+
schema and its relative file location when known. Use
|
|
835
|
+
`repository.collections.schema(name)` to inspect that schema.
|
|
836
|
+
The error code and effect remain available for programmatic handling; the
|
|
837
|
+
message does not include rejected field values.
|
|
838
|
+
|
|
839
|
+
Known field failures also expose optional `error.detail`, exported as
|
|
840
|
+
`DataValidationDetail`. It contains `record`, `field`, `reason`, and, when
|
|
841
|
+
known, `schemaFile` and `expected`. Reasons are `unknown_field`, `missing_field`,
|
|
842
|
+
`invalid_value`, and `source_schema_mismatch`. `expected` contains the field
|
|
843
|
+
`type`, `many`, and `nullable`, with `decimalPlaces` for a bounded number or
|
|
844
|
+
`format: 'month'` for a month date. Enum/constant values and rejected values
|
|
845
|
+
are excluded. Older errors without detail remain valid.
|
|
846
|
+
Other Data errors use this same optional `detail` field for local read/write
|
|
847
|
+
recovery or record-limit facts. Check `'reason' in error.detail` before reading
|
|
848
|
+
field-validation properties.
|
|
849
|
+
|
|
850
|
+
When handling a failure, preserve the SDK error's message. For example, a
|
|
851
|
+
routine that catches Data errors and reports them in its own result can keep
|
|
852
|
+
the explanation alongside the stable code:
|
|
853
|
+
|
|
854
|
+
```ts
|
|
855
|
+
import { DataOperationError } from '@oddington/sdk';
|
|
856
|
+
|
|
857
|
+
try {
|
|
858
|
+
await changes.validate();
|
|
859
|
+
} catch (error) {
|
|
860
|
+
if (!(error instanceof DataOperationError)) throw error;
|
|
861
|
+
return { code: error.code, message: error.message,
|
|
862
|
+
...(error.detail ? { detail: error.detail } : {}) };
|
|
863
|
+
}
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
When the routine cannot continue, rethrow the original error to preserve its
|
|
867
|
+
type and guidance. A new generic `Error` loses the SDK's bounded failure facts.
|
|
868
|
+
|
|
869
|
+
`apply_pending` is retryable and may report `uncertain` or `applied`;
|
|
758
870
|
retry the same change set to reconcile its retained identity. A transport failure
|
|
759
|
-
also keeps that identity. Concurrent
|
|
871
|
+
also keeps that identity. Concurrent `apply()` calls coalesce, confirmed calls
|
|
760
872
|
replay the result, and changed intent requires a new change set. Updates are
|
|
761
|
-
bounded with creates and deletions to 10,000 total operations
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
873
|
+
bounded with creates and deletions to 10,000 total operations, with one operation
|
|
874
|
+
per ref. The complete serialized change request is limited to 32 MiB for local
|
|
875
|
+
file changes and 256 KiB for hosted writes. Search and history are outside this
|
|
876
|
+
Changes slice. Source Records use ordinary schema rules. Files checks access
|
|
877
|
+
when a consumer reads the referenced content.
|
|
765
878
|
|
|
766
879
|
Bind a declared Airtable connection with `connections.airtable('project-management')`
|
|
767
880
|
from the package root. Import and binding perform no network or credential work.
|