@sequenceholdings/artifact-studio 0.3.1 → 0.5.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.
Files changed (108) hide show
  1. package/README.md +71 -0
  2. package/VERSION-PIN.md +9 -0
  3. package/dist/active-deploy-policy.d.ts +9 -3
  4. package/dist/active-deploy-policy.js +20 -8
  5. package/dist/artifact-env-bindings.d.ts +9 -0
  6. package/dist/artifact-env-bindings.js +32 -0
  7. package/dist/build.js +7 -0
  8. package/dist/cli.js +66 -13
  9. package/dist/deployment-validation.d.ts +2 -0
  10. package/dist/deployment-validation.js +2 -0
  11. package/dist/dev-link.js +29 -3
  12. package/dist/env-binding-scan.d.ts +10 -0
  13. package/dist/env-binding-scan.js +179 -0
  14. package/dist/env-bindings.d.ts +52 -0
  15. package/dist/env-bindings.js +180 -0
  16. package/dist/gateway/control.d.ts +33 -0
  17. package/dist/gateway/control.js +29 -0
  18. package/dist/gateway/hosted-proxy.d.ts +5 -0
  19. package/dist/gateway/hosted-proxy.js +4 -1
  20. package/dist/gateway/lattice-invoke.d.ts +48 -0
  21. package/dist/gateway/lattice-invoke.js +245 -0
  22. package/dist/gateway/lattice-upstream.d.ts +38 -0
  23. package/dist/gateway/lattice-upstream.js +136 -0
  24. package/dist/gateway/server.d.ts +3 -0
  25. package/dist/gateway/server.js +4 -5
  26. package/dist/gateway/session.d.ts +17 -2
  27. package/dist/gateway/session.js +27 -6
  28. package/dist/gateway-main.js +2 -0
  29. package/dist/loopback.d.ts +8 -0
  30. package/dist/loopback.js +13 -0
  31. package/dist/manifest-custom-roles.d.ts +11 -1
  32. package/dist/manifest-custom-roles.js +13 -5
  33. package/dist/manifest-next.d.ts +55 -0
  34. package/dist/manifest-next.js +35 -2
  35. package/dist/manifest-static.d.ts +35 -0
  36. package/dist/manifest-static.js +12 -0
  37. package/dist/manifest.d.ts +48 -0
  38. package/dist/next/protocol.js +2 -1
  39. package/dist/next-build.d.ts +2 -1
  40. package/dist/next-build.js +12 -7
  41. package/dist/next-dev.js +2 -1
  42. package/dist/next-project.js +2 -1
  43. package/dist/offline/cache.d.ts +4 -0
  44. package/dist/offline/cache.js +7 -0
  45. package/dist/offline/connector.d.ts +26 -0
  46. package/dist/offline/connector.js +118 -0
  47. package/dist/offline/crypto.d.ts +5 -0
  48. package/dist/offline/crypto.js +24 -0
  49. package/dist/offline/index.d.ts +8 -0
  50. package/dist/offline/index.js +7 -0
  51. package/dist/offline/indexed-db.d.ts +10 -0
  52. package/dist/offline/indexed-db.js +104 -0
  53. package/dist/offline/outbox.d.ts +7 -0
  54. package/dist/offline/outbox.js +46 -0
  55. package/dist/offline/replay.d.ts +25 -0
  56. package/dist/offline/replay.js +192 -0
  57. package/dist/offline/secure-store.d.ts +6 -0
  58. package/dist/offline/secure-store.js +51 -0
  59. package/dist/offline/types.d.ts +162 -0
  60. package/dist/offline/types.js +1 -0
  61. package/dist/orm-json-scalars.d.ts +21 -0
  62. package/dist/orm-json-scalars.js +21 -0
  63. package/dist/project.js +3 -3
  64. package/dist/role-definitions-notice.d.ts +7 -0
  65. package/dist/role-definitions-notice.js +19 -0
  66. package/dist/runtime-contracts.d.ts +7 -7
  67. package/dist/runtime-contracts.js +2 -1
  68. package/dist/sandbox-lint.js +1 -1
  69. package/dist/sdk-orm.d.ts +24 -0
  70. package/dist/sdk-orm.js +28 -0
  71. package/dist/sdk.d.ts +127 -0
  72. package/dist/sdk.js +1 -0
  73. package/dist/server/broker-contract.d.ts +191 -5
  74. package/dist/server/broker-contract.js +142 -1
  75. package/dist/server/client.d.ts +60 -12
  76. package/dist/server/client.js +143 -20
  77. package/dist/server/errors.d.ts +26 -0
  78. package/dist/server/errors.js +29 -0
  79. package/dist/server/managed-server-path.d.ts +6 -0
  80. package/dist/server/managed-server-path.js +14 -0
  81. package/dist/server/processes.d.ts +290 -0
  82. package/dist/server/processes.js +56 -0
  83. package/dist/server/request-context.d.ts +6 -0
  84. package/dist/server/request-context.js +12 -2
  85. package/dist/server/runtime-config.js +2 -1
  86. package/dist/server.d.ts +5 -1
  87. package/dist/server.js +1 -0
  88. package/dist/source-resolver.d.ts +9 -2
  89. package/dist/source-resolver.js +55 -8
  90. package/dist/templates/nextjs/AGENTS.md +10 -0
  91. package/dist/templates/nextjs/CLAUDE.md +1 -0
  92. package/dist/templates/nextjs/README.md +88 -0
  93. package/dist/templates/react-vite/AGENTS.md +8 -88
  94. package/dist/templates/react-vite/CLAUDE.md +0 -6
  95. package/dist/templates/react-vite/package.json +1 -1
  96. package/dist/templates/react-vite/src/App.tsx +37 -14
  97. package/dist/templates/react-vite/src/styles.css +10 -0
  98. package/dist/tunnel/client.js +10 -3
  99. package/dist/tunnel/protocol.js +2 -1
  100. package/package.json +7 -3
  101. package/templates/nextjs/AGENTS.md +10 -0
  102. package/templates/nextjs/CLAUDE.md +1 -0
  103. package/templates/nextjs/README.md +88 -0
  104. package/templates/react-vite/AGENTS.md +8 -88
  105. package/templates/react-vite/CLAUDE.md +0 -6
  106. package/templates/react-vite/package.json +1 -1
  107. package/templates/react-vite/src/App.tsx +37 -14
  108. package/templates/react-vite/src/styles.css +10 -0
@@ -0,0 +1,162 @@
1
+ import type { SequenceApiFetchResponse, SequenceOfflineNetwork, SequenceOfflineRequest } from '../sdk.js';
2
+ export type OfflineStoreName = 'cache' | 'outbox' | 'accesslog';
3
+ export interface OfflineKvStore {
4
+ get(input: {
5
+ store: OfflineStoreName;
6
+ key: string;
7
+ }): Promise<unknown>;
8
+ set(input: {
9
+ store: OfflineStoreName;
10
+ key: string;
11
+ value: unknown;
12
+ }): Promise<void>;
13
+ delete(input: {
14
+ store: OfflineStoreName;
15
+ key: string;
16
+ }): Promise<void>;
17
+ keys(input: {
18
+ store: OfflineStoreName;
19
+ }): Promise<string[]>;
20
+ }
21
+ export interface OfflineEncryptedRecord {
22
+ iv: number[];
23
+ ciphertext: number[];
24
+ }
25
+ export interface OfflineCrypto {
26
+ encrypt(input: {
27
+ value: unknown;
28
+ }): Promise<OfflineEncryptedRecord>;
29
+ decrypt(input: {
30
+ record: OfflineEncryptedRecord;
31
+ }): Promise<unknown>;
32
+ }
33
+ export interface EncryptedOfflineStore {
34
+ get<T = unknown>(input: {
35
+ store: OfflineStoreName;
36
+ key: string;
37
+ }): Promise<T | undefined>;
38
+ set(input: {
39
+ store: OfflineStoreName;
40
+ key: string;
41
+ value: unknown;
42
+ }): Promise<void>;
43
+ delete(input: {
44
+ store: OfflineStoreName;
45
+ key: string;
46
+ }): Promise<void>;
47
+ keys(input: {
48
+ store: OfflineStoreName;
49
+ }): Promise<string[]>;
50
+ clear(input: {
51
+ store: OfflineStoreName;
52
+ }): Promise<void>;
53
+ }
54
+ export type OfflineFailureKind = 'transient' | 'invalid' | 'auth' | 'conflict';
55
+ export type OfflineOutboxStatus = 'pending' | 'inflight' | 'failed' | 'conflict';
56
+ export interface OfflineConflictField {
57
+ key: string;
58
+ mine: unknown;
59
+ theirs: unknown;
60
+ }
61
+ export interface OfflineConflictInfo {
62
+ fields: OfflineConflictField[];
63
+ observedAt?: string;
64
+ serverUpdatedAt?: string;
65
+ }
66
+ export interface OfflineOutboxEntry {
67
+ id: string;
68
+ sequence: number;
69
+ request: SequenceOfflineRequest;
70
+ entity: string;
71
+ capturedAt: string;
72
+ attempts: number;
73
+ status: OfflineOutboxStatus;
74
+ observed?: Record<string, unknown>;
75
+ optimisticId?: string;
76
+ failureKind?: OfflineFailureKind;
77
+ lastError?: string;
78
+ conflict?: OfflineConflictInfo;
79
+ force?: boolean;
80
+ }
81
+ export interface OfflineOutbox {
82
+ enqueue(input: {
83
+ id?: string;
84
+ request: SequenceOfflineRequest;
85
+ entity: string;
86
+ observed?: Record<string, unknown>;
87
+ optimisticId?: string;
88
+ }): Promise<OfflineOutboxEntry>;
89
+ list(): Promise<OfflineOutboxEntry[]>;
90
+ update(input: {
91
+ id: string;
92
+ patch: Partial<OfflineOutboxEntry>;
93
+ }): Promise<void>;
94
+ remove(input: {
95
+ id: string;
96
+ }): Promise<void>;
97
+ }
98
+ export interface OfflineReadDescriptor {
99
+ key: string;
100
+ ttlMs: number;
101
+ entity: string;
102
+ }
103
+ export interface OfflineReadPolicy {
104
+ match(request: SequenceOfflineRequest): OfflineReadDescriptor | null;
105
+ }
106
+ export interface OfflineCacheEntry {
107
+ response: SequenceApiFetchResponse;
108
+ cachedAt: number;
109
+ expiresAt: number;
110
+ entity: string;
111
+ }
112
+ export interface OfflineCache {
113
+ read(input: {
114
+ key: string;
115
+ }): Promise<OfflineCacheEntry | undefined>;
116
+ write(input: {
117
+ key: string;
118
+ entry: OfflineCacheEntry;
119
+ }): Promise<void>;
120
+ remove(input: {
121
+ key: string;
122
+ }): Promise<void>;
123
+ }
124
+ export interface OfflineMutationDescriptor {
125
+ entity: string;
126
+ label(entry: OfflineOutboxEntry): string;
127
+ capture?(input: {
128
+ request: SequenceOfflineRequest;
129
+ }): Promise<Record<string, unknown> | undefined>;
130
+ optimisticApply?(input: {
131
+ entry: OfflineOutboxEntry;
132
+ }): Promise<void> | void;
133
+ optimisticResponse?(input: {
134
+ entry: OfflineOutboxEntry;
135
+ }): SequenceApiFetchResponse;
136
+ fetchCurrent?(input: {
137
+ entry: OfflineOutboxEntry;
138
+ network: SequenceOfflineNetwork;
139
+ }): Promise<unknown>;
140
+ detectConflict?(input: {
141
+ entry: OfflineOutboxEntry;
142
+ current: unknown;
143
+ }): OfflineConflictInfo | null;
144
+ reconcile?(input: {
145
+ entry: OfflineOutboxEntry;
146
+ response: SequenceApiFetchResponse;
147
+ }): Promise<void> | void;
148
+ rollback?(input: {
149
+ entry: OfflineOutboxEntry;
150
+ }): Promise<void> | void;
151
+ }
152
+ export interface OfflineMutationPolicy {
153
+ match(request: SequenceOfflineRequest): OfflineMutationDescriptor | null;
154
+ }
155
+ export interface OfflineUnresolvedWrite {
156
+ id: string;
157
+ label: string;
158
+ capturedAt: string;
159
+ kind: OfflineFailureKind;
160
+ error?: string;
161
+ conflict?: OfflineConflictInfo;
162
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The ORM data plane's opt-in exact-number wire form (orm-data-plane/src/protocol.ts).
3
+ * By default numeric and bigint values come back as JSON numbers, which JavaScript
4
+ * rounds past 15-17 significant digits. With `lossless-v1` they come back as strings
5
+ * ("250000.00", "9007199254740993"), and a successful response acknowledges the form
6
+ * with the same header. Variables may carry numeric and bigint values as decimal
7
+ * strings in either form.
8
+ */
9
+ export declare const ORM_JSON_SCALARS_HEADER = "x-sequence-orm-json-scalars";
10
+ export declare const ORM_LOSSLESS_JSON_SCALARS = "lossless-v1";
11
+ export type ArtifactOrmJsonScalars = typeof ORM_LOSSLESS_JSON_SCALARS;
12
+ /**
13
+ * An executor that asked for exact numbers refuses a successful result the platform
14
+ * did not acknowledge, because its numbers may already be rounded. Failed responses
15
+ * never acknowledge and keep their own error.
16
+ */
17
+ export declare function assertJsonScalarsAcknowledged({ requested, acknowledged, status }: {
18
+ requested: ArtifactOrmJsonScalars | undefined;
19
+ acknowledged: string | null | undefined;
20
+ status: number;
21
+ }): void;
@@ -0,0 +1,21 @@
1
+ import { OrmOperationError } from '@sequenceholdings/orm/client';
2
+ /**
3
+ * The ORM data plane's opt-in exact-number wire form (orm-data-plane/src/protocol.ts).
4
+ * By default numeric and bigint values come back as JSON numbers, which JavaScript
5
+ * rounds past 15-17 significant digits. With `lossless-v1` they come back as strings
6
+ * ("250000.00", "9007199254740993"), and a successful response acknowledges the form
7
+ * with the same header. Variables may carry numeric and bigint values as decimal
8
+ * strings in either form.
9
+ */
10
+ export const ORM_JSON_SCALARS_HEADER = 'x-sequence-orm-json-scalars';
11
+ export const ORM_LOSSLESS_JSON_SCALARS = 'lossless-v1';
12
+ /**
13
+ * An executor that asked for exact numbers refuses a successful result the platform
14
+ * did not acknowledge, because its numbers may already be rounded. Failed responses
15
+ * never acknowledge and keep their own error.
16
+ */
17
+ export function assertJsonScalarsAcknowledged({ requested, acknowledged, status }) {
18
+ if (requested === undefined || acknowledged === requested)
19
+ return;
20
+ throw new OrmOperationError(`Atlas did not confirm ${requested} numbers for this ORM response, so its numeric and bigint values may be rounded; update Atlas before relying on exact numbers`, status, 'LOSSLESS_TRANSPORT_UNAVAILABLE');
21
+ }
package/dist/project.js CHANGED
@@ -9,7 +9,7 @@ import { readNextSourceSnapshot } from './next-source.js';
9
9
  import { normalizeArtifactPath } from './paths.js';
10
10
  const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', '.next', '.artifact-studio']);
11
11
  const TEXT_EXTENSIONS = new Set([
12
- '.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.html', '.md', '.yml', '.yaml', '.svg',
12
+ '.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json', '.css', '.html', '.md', '.yml', '.yaml', '.svg',
13
13
  ]);
14
14
  export function hasArtifactStudioManifest(rootDir) {
15
15
  return existsSync(join(resolve(rootDir), 'artifact.bundle.yml'));
@@ -83,9 +83,9 @@ function isTextFile(path) {
83
83
  TEXT_EXTENSIONS.has(ext));
84
84
  }
85
85
  function inferContentType(path) {
86
- if (path.endsWith('.tsx') || path.endsWith('.ts'))
86
+ if (/\.(?:tsx?|mts|cts)$/.test(path))
87
87
  return 'text/typescript';
88
- if (path.endsWith('.jsx') || path.endsWith('.js'))
88
+ if (/\.(?:jsx?|mjs|cjs)$/.test(path))
89
89
  return 'text/javascript';
90
90
  if (path.endsWith('.css'))
91
91
  return 'text/css';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Capabilities are code, roles are data: roles are created in Atlas, and
3
+ * `capabilities.roles.definitions` here is a deprecated one-time seed (a
4
+ * deploy creates any that don't exist yet and never updates them). Atlas only
5
+ * logs the drift, so tell the author.
6
+ */
7
+ export declare function warnOnRoleDefinitions(manifest: unknown, warn?: (line: string) => void): void;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Capabilities are code, roles are data: roles are created in Atlas, and
3
+ * `capabilities.roles.definitions` here is a deprecated one-time seed (a
4
+ * deploy creates any that don't exist yet and never updates them). Atlas only
5
+ * logs the drift, so tell the author.
6
+ */
7
+ export function warnOnRoleDefinitions(manifest, warn = console.warn) {
8
+ // A non-null, non-array object is safe to index by key; TS can't narrow `object` to a record itself.
9
+ const record = (value) => typeof value === 'object' && value !== null && !Array.isArray(value) ? value : null;
10
+ const definitions = record(record(record(record(manifest)?.capabilities)?.roles)?.definitions);
11
+ if (!definitions)
12
+ return;
13
+ const keys = Object.keys(definitions).sort();
14
+ if (keys.length === 0)
15
+ return;
16
+ warn(`[seq-studio] artifact.bundle.yml defines roles (${keys.join(', ')}). Roles in YAML are a deprecated one-time seed: ` +
17
+ `a deploy creates any that don't exist yet and never updates them after. Create and edit roles in Atlas ` +
18
+ `(Permissions → Roles); keep capabilities in YAML.`);
19
+ }
@@ -72,9 +72,9 @@ export declare const artifactOperationSchema: z.ZodDiscriminatedUnion<[z.ZodObje
72
72
  projectId: z.ZodString;
73
73
  environment: z.ZodString;
74
74
  kind: z.ZodEnum<{
75
- preview: "preview";
76
75
  build: "build";
77
76
  deploy: "deploy";
77
+ preview: "preview";
78
78
  rollback: "rollback";
79
79
  suspend: "suspend";
80
80
  delete: "delete";
@@ -92,9 +92,9 @@ export declare const artifactOperationSchema: z.ZodDiscriminatedUnion<[z.ZodObje
92
92
  projectId: z.ZodString;
93
93
  environment: z.ZodString;
94
94
  kind: z.ZodEnum<{
95
- preview: "preview";
96
95
  build: "build";
97
96
  deploy: "deploy";
97
+ preview: "preview";
98
98
  rollback: "rollback";
99
99
  suspend: "suspend";
100
100
  delete: "delete";
@@ -109,9 +109,9 @@ export declare const artifactOperationSchema: z.ZodDiscriminatedUnion<[z.ZodObje
109
109
  projectId: z.ZodString;
110
110
  environment: z.ZodString;
111
111
  kind: z.ZodEnum<{
112
- preview: "preview";
113
112
  build: "build";
114
113
  deploy: "deploy";
114
+ preview: "preview";
115
115
  rollback: "rollback";
116
116
  suspend: "suspend";
117
117
  delete: "delete";
@@ -130,9 +130,9 @@ export declare const artifactOperationSchema: z.ZodDiscriminatedUnion<[z.ZodObje
130
130
  projectId: z.ZodString;
131
131
  environment: z.ZodString;
132
132
  kind: z.ZodEnum<{
133
- preview: "preview";
134
133
  build: "build";
135
134
  deploy: "deploy";
135
+ preview: "preview";
136
136
  rollback: "rollback";
137
137
  suspend: "suspend";
138
138
  delete: "delete";
@@ -147,9 +147,9 @@ export declare const artifactOperationSchema: z.ZodDiscriminatedUnion<[z.ZodObje
147
147
  projectId: z.ZodString;
148
148
  environment: z.ZodString;
149
149
  kind: z.ZodEnum<{
150
- preview: "preview";
151
150
  build: "build";
152
151
  deploy: "deploy";
152
+ preview: "preview";
153
153
  rollback: "rollback";
154
154
  suspend: "suspend";
155
155
  delete: "delete";
@@ -164,9 +164,9 @@ export declare const artifactRuntimeSessionSchema: z.ZodObject<{
164
164
  projectId: z.ZodString;
165
165
  environment: z.ZodString;
166
166
  mode: z.ZodEnum<{
167
+ local: "local";
167
168
  preview: "preview";
168
169
  published: "published";
169
- local: "local";
170
170
  }>;
171
171
  origin: z.ZodString;
172
172
  parentOrigin: z.ZodString;
@@ -187,12 +187,12 @@ export declare const artifactLocalDevSessionSchema: z.ZodObject<{
187
187
  sourceRevision: z.ZodOptional<z.ZodString>;
188
188
  revision: z.ZodNumber;
189
189
  state: z.ZodEnum<{
190
+ offline: "offline";
190
191
  failed: "failed";
191
192
  starting: "starting";
192
193
  compiling: "compiling";
193
194
  ready: "ready";
194
195
  reconnecting: "reconnecting";
195
- offline: "offline";
196
196
  revoked: "revoked";
197
197
  expired: "expired";
198
198
  }>;
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { ARTIFACT_NEXT_RUNTIME, artifactNextSecretNameSchema } from './manifest-next.js';
3
3
  import { MAX_STUDIO_PATH_LENGTH } from './paths.js';
4
+ import { isLoopbackHostname } from './loopback.js';
4
5
  export { resolveArtifactNextImages } from './next-image-config.js';
5
6
  /** Shared by local collection, repository collection and the bounded source-upload API. */
6
7
  export const ARTIFACT_NEXT_SOURCE_LIMITS = Object.freeze({
@@ -42,7 +43,7 @@ export const artifactSourcePathSchema = z.string().min(1).max(MAX_STUDIO_PATH_LE
42
43
  export const artifactRuntimeOriginSchema = z.string().max(2048).refine((value) => {
43
44
  try {
44
45
  const url = new URL(value);
45
- return url.protocol === 'https:' && url.origin === value && !url.hostname.includes('*');
46
+ return (url.protocol === 'https:' || (url.protocol === 'http:' && isLoopbackHostname(url.hostname))) && url.origin === value && !url.hostname.includes('*');
46
47
  }
47
48
  catch {
48
49
  return false;
@@ -42,7 +42,7 @@ const RULES = [
42
42
  message: "Direct fetch() to an external origin is blocked (CSP connect-src 'self') — call Atlas routes via seq.api.fetch.",
43
43
  },
44
44
  ];
45
- const LINTABLE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.css', '.html']);
45
+ const LINTABLE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.css', '.html']);
46
46
  function extensionOf(path) {
47
47
  return path.includes('.') ? path.slice(path.lastIndexOf('.')) : '';
48
48
  }
@@ -0,0 +1,24 @@
1
+ import { type OperationExecutor } from '@sequenceholdings/orm/client';
2
+ import { type ArtifactOrmJsonScalars } from './orm-json-scalars.js';
3
+ import { type SequenceApi } from './sdk.js';
4
+ export interface ArtifactOrmExecutorConfig {
5
+ namespace: string;
6
+ /** The generated operations.manifest.json, validated before any host call. */
7
+ manifest: unknown;
8
+ /** Optional action-scoped host client (for example, withTraceAction's client). */
9
+ sdk?: {
10
+ api: Pick<SequenceApi, 'fetch'>;
11
+ };
12
+ /**
13
+ * `lossless-v1` returns numeric and bigint values as strings instead of JSON numbers.
14
+ * The executor refuses any successful result the host does not acknowledge in that form.
15
+ */
16
+ jsonScalars?: ArtifactOrmJsonScalars;
17
+ }
18
+ /**
19
+ * Execute persisted operations as the signed-in Artifact user. The host owns
20
+ * authentication; this helper never obtains or exposes browser credentials.
21
+ * Declare the namespace path in capabilities.api.write (queries also use POST).
22
+ * Namespace grants, capabilities, row policies, and write checks still apply.
23
+ */
24
+ export declare function createArtifactOrmExecutor({ namespace, manifest, sdk, jsonScalars }: ArtifactOrmExecutorConfig): OperationExecutor;
@@ -0,0 +1,28 @@
1
+ import { createOperationExecutorWithTransport, OrmClientError } from '@sequenceholdings/orm/client';
2
+ import { assertJsonScalarsAcknowledged, ORM_JSON_SCALARS_HEADER } from './orm-json-scalars.js';
3
+ import { seq } from './sdk.js';
4
+ /**
5
+ * Execute persisted operations as the signed-in Artifact user. The host owns
6
+ * authentication; this helper never obtains or exposes browser credentials.
7
+ * Declare the namespace path in capabilities.api.write (queries also use POST).
8
+ * Namespace grants, capabilities, row policies, and write checks still apply.
9
+ */
10
+ export function createArtifactOrmExecutor({ namespace, manifest, sdk = seq, jsonScalars }) {
11
+ if (!sdk || typeof sdk.api?.fetch !== 'function') {
12
+ throw new OrmClientError('Artifact ORM requires the authenticated Artifact Studio host; open the deployed artifact or its Preview');
13
+ }
14
+ return createOperationExecutorWithTransport({
15
+ namespace,
16
+ manifest,
17
+ transport: async ({ namespace: target, op, variables, idempotencyKey }) => {
18
+ const response = await sdk.api.fetch(`/api/data/v2/${target}`, {
19
+ method: 'POST',
20
+ body: { op, variables, ...(idempotencyKey === undefined ? {} : { idempotencyKey }) },
21
+ ...(jsonScalars ? { headers: { [ORM_JSON_SCALARS_HEADER]: jsonScalars } } : {}),
22
+ });
23
+ if (response.ok)
24
+ assertJsonScalarsAcknowledged({ requested: jsonScalars, acknowledged: response.headers?.[ORM_JSON_SCALARS_HEADER], status: response.status });
25
+ return response;
26
+ },
27
+ });
28
+ }
package/dist/sdk.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { type StartStorageUpload, type StorageUploadSession } from '@sequenceholdings/storage';
2
+ export { createArtifactOrmExecutor, type ArtifactOrmExecutorConfig } from './sdk-orm.js';
3
+ export type { ArtifactOrmJsonScalars } from './orm-json-scalars.js';
2
4
  export interface SequenceStorageOptions {
3
5
  signal?: AbortSignal;
4
6
  timeoutMs?: number;
@@ -47,6 +49,54 @@ export interface SequenceApi {
47
49
  patch<TBody = unknown>(path: string, body?: unknown): Promise<TBody>;
48
50
  delete<TBody = unknown>(path: string, body?: unknown): Promise<TBody>;
49
51
  }
52
+ export type SequenceOfflineRequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
53
+ export interface SequenceOfflineRequest {
54
+ method: SequenceOfflineRequestMethod;
55
+ path: string;
56
+ body?: unknown;
57
+ headers?: Record<string, string>;
58
+ }
59
+ export interface SequenceOfflineConnectorExecution<TBody = unknown> {
60
+ request: SequenceOfflineRequest;
61
+ /**
62
+ * Invoke the authenticated Artifact Studio bridge. Authentication remains
63
+ * host-owned and is never exposed to the connector or durable storage.
64
+ */
65
+ network: (request: SequenceOfflineRequest) => Promise<SequenceApiFetchResponse<TBody>>;
66
+ }
67
+ export interface SequenceOfflineNetwork {
68
+ execute<TBody = unknown>(request: SequenceOfflineRequest): Promise<SequenceApiFetchResponse<TBody>>;
69
+ }
70
+ export interface SequenceOfflineRequestConnector {
71
+ execute<TBody = unknown>(execution: SequenceOfflineConnectorExecution<TBody>): Promise<SequenceApiFetchResponse<TBody>>;
72
+ /**
73
+ * Receives a capability-gated bridge executor for durable outbox replay.
74
+ * It carries no bearer token and remains subject to the active manifest.
75
+ */
76
+ attach?(context: {
77
+ network: SequenceOfflineNetwork;
78
+ }): void | (() => void);
79
+ }
80
+ export interface SequenceOffline {
81
+ /**
82
+ * Install the artifact's request policy beneath seq.api. Returns a disposer
83
+ * that restores the network-only default when it still owns registration.
84
+ */
85
+ registerConnector(registration: {
86
+ connector: SequenceOfflineRequestConnector;
87
+ }): () => void;
88
+ /** Cache the current artifact shell on this device after the user's opt-in. */
89
+ enroll(options?: {
90
+ maxAgeHours?: number;
91
+ }): Promise<void>;
92
+ /** Remove this artifact's cached shell. App-owned data should be wiped too. */
93
+ remove(): Promise<void>;
94
+ status(): Promise<{
95
+ enabled: boolean;
96
+ enrolled: boolean;
97
+ expiresAt?: number;
98
+ }>;
99
+ }
50
100
  export interface SequenceFunctions {
51
101
  invoke<TOutput = unknown>(id: string, params?: Record<string, unknown>, traceAction?: {
52
102
  id: string;
@@ -134,6 +184,7 @@ export interface SequenceHost {
134
184
  export interface SequenceArtifactSdk {
135
185
  storage: SequenceStorage;
136
186
  api: SequenceApi;
187
+ offline: SequenceOffline;
137
188
  ai: {
138
189
  fetch(path: string, options?: SequenceApiFetchOptions): Promise<Response>;
139
190
  };
@@ -210,6 +261,73 @@ export declare function createArtifactStorageClient(sdk?: SequenceArtifactSdk):
210
261
  versionId: string | null;
211
262
  maxBytes: number;
212
263
  }>;
264
+ startBatch: (items: StartStorageUpload[]) => Promise<{
265
+ items: {
266
+ id: string;
267
+ fileId: string;
268
+ uploadUrl: string | null;
269
+ expiresAt: string;
270
+ state: "pending" | "complete" | "aborted";
271
+ versionId: string | null;
272
+ maxBytes: number;
273
+ }[];
274
+ }>;
275
+ list: () => Promise<{
276
+ items: {
277
+ id: string;
278
+ fileId: string;
279
+ name: string;
280
+ state: "pending" | "complete" | "aborted";
281
+ expiresAt: string;
282
+ maxBytes: number;
283
+ expectedSize: number | null;
284
+ versionId: string | null;
285
+ }[];
286
+ limits: {
287
+ perPrincipal: number;
288
+ perWorkspace: number;
289
+ };
290
+ }>;
291
+ reconcile: (input?: {
292
+ abortIncomplete?: boolean;
293
+ }) => Promise<{
294
+ completed: {
295
+ id: string;
296
+ fileId: string;
297
+ name: string;
298
+ state: "pending" | "complete" | "aborted";
299
+ expiresAt: string;
300
+ maxBytes: number;
301
+ expectedSize: number | null;
302
+ versionId: string | null;
303
+ code: string;
304
+ message: string;
305
+ }[];
306
+ aborted: {
307
+ id: string;
308
+ fileId: string;
309
+ name: string;
310
+ state: "pending" | "complete" | "aborted";
311
+ expiresAt: string;
312
+ maxBytes: number;
313
+ expectedSize: number | null;
314
+ versionId: string | null;
315
+ code: string;
316
+ message: string;
317
+ }[];
318
+ pending: {
319
+ id: string;
320
+ fileId: string;
321
+ name: string;
322
+ state: "pending" | "complete" | "aborted";
323
+ expiresAt: string;
324
+ maxBytes: number;
325
+ expectedSize: number | null;
326
+ versionId: string | null;
327
+ code: string;
328
+ message: string;
329
+ }[];
330
+ }>;
213
331
  };
214
332
  get: () => Promise<{
215
333
  id: string;
@@ -488,6 +606,15 @@ export declare function createArtifactStorageClient(sdk?: SequenceArtifactSdk):
488
606
  capabilities: () => Promise<{
489
607
  canProvision: boolean;
490
608
  uploadMethods: string[];
609
+ pendingUploads: {
610
+ perPrincipal: number;
611
+ perWorkspace: number;
612
+ };
613
+ uploadBatchLimit: number;
614
+ importConcurrency: {
615
+ default: number;
616
+ max: number;
617
+ };
491
618
  }>;
492
619
  list: (options?: import("@sequenceholdings/storage").StorageListOptions) => Promise<{
493
620
  items: {
package/dist/sdk.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { storageClient } from '@sequenceholdings/storage';
2
+ export { createArtifactOrmExecutor } from './sdk-orm.js';
2
3
  export const seq = typeof window !== 'undefined'
3
4
  ? window.seq
4
5
  : undefined;