@dxos/assistant 0.7.4-df37807

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 (128) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +24 -0
  3. package/dist/lib/browser/chunk-AKP45LQH.mjs +1394 -0
  4. package/dist/lib/browser/chunk-AKP45LQH.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +71 -0
  6. package/dist/lib/browser/index.mjs.map +7 -0
  7. package/dist/lib/browser/meta.json +1 -0
  8. package/dist/lib/browser/testing/index.mjs +601 -0
  9. package/dist/lib/browser/testing/index.mjs.map +7 -0
  10. package/dist/lib/node/chunk-G6CGWEMO.cjs +1418 -0
  11. package/dist/lib/node/chunk-G6CGWEMO.cjs.map +7 -0
  12. package/dist/lib/node/index.cjs +93 -0
  13. package/dist/lib/node/index.cjs.map +7 -0
  14. package/dist/lib/node/meta.json +1 -0
  15. package/dist/lib/node/testing/index.cjs +619 -0
  16. package/dist/lib/node/testing/index.cjs.map +7 -0
  17. package/dist/lib/node-esm/chunk-WUPUPEFB.mjs +1396 -0
  18. package/dist/lib/node-esm/chunk-WUPUPEFB.mjs.map +7 -0
  19. package/dist/lib/node-esm/index.mjs +72 -0
  20. package/dist/lib/node-esm/index.mjs.map +7 -0
  21. package/dist/lib/node-esm/meta.json +1 -0
  22. package/dist/lib/node-esm/testing/index.mjs +602 -0
  23. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  24. package/dist/types/src/ai-service/client.d.ts +48 -0
  25. package/dist/types/src/ai-service/client.d.ts.map +1 -0
  26. package/dist/types/src/ai-service/client.test.d.ts +2 -0
  27. package/dist/types/src/ai-service/client.test.d.ts.map +1 -0
  28. package/dist/types/src/ai-service/index.d.ts +3 -0
  29. package/dist/types/src/ai-service/index.d.ts.map +1 -0
  30. package/dist/types/src/ai-service/schema.d.ts +296 -0
  31. package/dist/types/src/ai-service/schema.d.ts.map +1 -0
  32. package/dist/types/src/ai-service/sse.d.ts +34 -0
  33. package/dist/types/src/ai-service/sse.d.ts.map +1 -0
  34. package/dist/types/src/bin/chat.d.ts +2 -0
  35. package/dist/types/src/bin/chat.d.ts.map +1 -0
  36. package/dist/types/src/conversation/conversation.d.ts +23 -0
  37. package/dist/types/src/conversation/conversation.d.ts.map +1 -0
  38. package/dist/types/src/conversation/conversation.test.d.ts +2 -0
  39. package/dist/types/src/conversation/conversation.test.d.ts.map +1 -0
  40. package/dist/types/src/conversation/index.d.ts +3 -0
  41. package/dist/types/src/conversation/index.d.ts.map +1 -0
  42. package/dist/types/src/conversation/types.d.ts +48 -0
  43. package/dist/types/src/conversation/types.d.ts.map +1 -0
  44. package/dist/types/src/cypher/ast.d.ts +56 -0
  45. package/dist/types/src/cypher/ast.d.ts.map +1 -0
  46. package/dist/types/src/cypher/combinators.d.ts +14 -0
  47. package/dist/types/src/cypher/combinators.d.ts.map +1 -0
  48. package/dist/types/src/cypher/combinators.test.d.ts +2 -0
  49. package/dist/types/src/cypher/combinators.test.d.ts.map +1 -0
  50. package/dist/types/src/cypher/echo-data-source.d.ts +16 -0
  51. package/dist/types/src/cypher/echo-data-source.d.ts.map +1 -0
  52. package/dist/types/src/cypher/execution-plan.d.ts +49 -0
  53. package/dist/types/src/cypher/execution-plan.d.ts.map +1 -0
  54. package/dist/types/src/cypher/execution-plan.test.d.ts +2 -0
  55. package/dist/types/src/cypher/execution-plan.test.d.ts.map +1 -0
  56. package/dist/types/src/cypher/index.d.ts +5 -0
  57. package/dist/types/src/cypher/index.d.ts.map +1 -0
  58. package/dist/types/src/cypher/parjs.d.ts +4 -0
  59. package/dist/types/src/cypher/parjs.d.ts.map +1 -0
  60. package/dist/types/src/cypher/parser.d.ts +6 -0
  61. package/dist/types/src/cypher/parser.d.ts.map +1 -0
  62. package/dist/types/src/cypher/query-executor.d.ts +26 -0
  63. package/dist/types/src/cypher/query-executor.d.ts.map +1 -0
  64. package/dist/types/src/cypher/query-executor.test.d.ts +2 -0
  65. package/dist/types/src/cypher/query-executor.test.d.ts.map +1 -0
  66. package/dist/types/src/cypher/schema.d.ts +12 -0
  67. package/dist/types/src/cypher/schema.d.ts.map +1 -0
  68. package/dist/types/src/cypher/schema.test.d.ts +2 -0
  69. package/dist/types/src/cypher/schema.test.d.ts.map +1 -0
  70. package/dist/types/src/index.d.ts +4 -0
  71. package/dist/types/src/index.d.ts.map +1 -0
  72. package/dist/types/src/sanity.test.d.ts +2 -0
  73. package/dist/types/src/sanity.test.d.ts.map +1 -0
  74. package/dist/types/src/testing/index.d.ts +5 -0
  75. package/dist/types/src/testing/index.d.ts.map +1 -0
  76. package/dist/types/src/testing/logger.d.ts +12 -0
  77. package/dist/types/src/testing/logger.d.ts.map +1 -0
  78. package/dist/types/src/testing/object-extraction.test.d.ts +1 -0
  79. package/dist/types/src/testing/object-extraction.test.d.ts.map +1 -0
  80. package/dist/types/src/testing/query-promts.d.ts +5 -0
  81. package/dist/types/src/testing/query-promts.d.ts.map +1 -0
  82. package/dist/types/src/testing/query.test.d.ts +2 -0
  83. package/dist/types/src/testing/query.test.d.ts.map +1 -0
  84. package/dist/types/src/testing/test-data.d.ts +29 -0
  85. package/dist/types/src/testing/test-data.d.ts.map +1 -0
  86. package/dist/types/src/testing/test-schema.d.ts +50 -0
  87. package/dist/types/src/testing/test-schema.d.ts.map +1 -0
  88. package/dist/types/src/util/index.d.ts +2 -0
  89. package/dist/types/src/util/index.d.ts.map +1 -0
  90. package/dist/types/src/util/trim.d.ts +5 -0
  91. package/dist/types/src/util/trim.d.ts.map +1 -0
  92. package/dist/types/tsconfig.tsbuildinfo +1 -0
  93. package/package.json +64 -0
  94. package/src/ai-service/client.test.ts +162 -0
  95. package/src/ai-service/client.ts +243 -0
  96. package/src/ai-service/index.ts +6 -0
  97. package/src/ai-service/schema.ts +260 -0
  98. package/src/ai-service/sse.ts +336 -0
  99. package/src/bin/chat.ts +79 -0
  100. package/src/conversation/conversation.test.ts +74 -0
  101. package/src/conversation/conversation.ts +134 -0
  102. package/src/conversation/index.ts +6 -0
  103. package/src/conversation/types.ts +90 -0
  104. package/src/cypher/ast.ts +75 -0
  105. package/src/cypher/combinators.test.ts +137 -0
  106. package/src/cypher/combinators.ts +149 -0
  107. package/src/cypher/echo-data-source.ts +105 -0
  108. package/src/cypher/execution-plan.test.ts +42 -0
  109. package/src/cypher/execution-plan.ts +208 -0
  110. package/src/cypher/index.ts +8 -0
  111. package/src/cypher/parjs-shims.d.ts +6 -0
  112. package/src/cypher/parjs.ts +10 -0
  113. package/src/cypher/parser.ts +19 -0
  114. package/src/cypher/query-executor.test.ts +47 -0
  115. package/src/cypher/query-executor.ts +145 -0
  116. package/src/cypher/schema.test.ts +17 -0
  117. package/src/cypher/schema.ts +82 -0
  118. package/src/index.ts +7 -0
  119. package/src/sanity.test.ts +9 -0
  120. package/src/testing/index.ts +8 -0
  121. package/src/testing/logger.ts +128 -0
  122. package/src/testing/object-extraction.test.ts +71 -0
  123. package/src/testing/query-promts.ts +75 -0
  124. package/src/testing/query.test.ts +90 -0
  125. package/src/testing/test-data.ts +366 -0
  126. package/src/testing/test-schema.ts +80 -0
  127. package/src/util/index.ts +5 -0
  128. package/src/util/trim.ts +15 -0
@@ -0,0 +1,366 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Schema as S } from '@effect/schema';
6
+
7
+ import { raise } from '@dxos/debug';
8
+ import { JSON_SCHEMA_ECHO_REF_ID, ObjectId, toJsonSchema, type JsonSchemaType, type Ref } from '@dxos/echo-schema';
9
+ import { invariant } from '@dxos/invariant';
10
+ import { create, makeRef } from '@dxos/live-object';
11
+
12
+ import {
13
+ type DataSource,
14
+ type Node,
15
+ type Relationship,
16
+ formatInferredRelationshipLabel,
17
+ formatNodeLabel,
18
+ } from '../cypher';
19
+ import { Contact, Org, Project, Task } from '../testing';
20
+ import type { EchoDatabase } from '@dxos/echo-db';
21
+
22
+ export const seedTestData = (db: EchoDatabase) => {
23
+ const contactRich = db.add(
24
+ create(Contact, {
25
+ name: 'Rich',
26
+ }),
27
+ );
28
+ const contactJosiah = db.add(
29
+ create(Contact, {
30
+ name: 'Josiah',
31
+ }),
32
+ );
33
+ const contactDima = db.add(
34
+ create(Contact, {
35
+ name: 'Dima',
36
+ }),
37
+ );
38
+ const contactFred = db.add(
39
+ create(Contact, {
40
+ name: 'Fred',
41
+ }),
42
+ );
43
+
44
+ const projectComposer = db.add(
45
+ create(Project, {
46
+ name: 'Composer',
47
+ }),
48
+ );
49
+ const projectEcho = db.add(
50
+ create(Project, {
51
+ name: 'ECHO',
52
+ }),
53
+ );
54
+ const projectDoodles = db.add(
55
+ create(Project, {
56
+ name: 'Doodles',
57
+ }),
58
+ );
59
+
60
+ const _taskComposer1 = db.add(
61
+ create(Task, {
62
+ name: 'Optimize startup performance',
63
+ project: makeRef(projectComposer),
64
+ assignee: makeRef(contactJosiah),
65
+ }),
66
+ );
67
+ const _taskComposer2 = db.add(
68
+ create(Task, {
69
+ name: 'Create form builder',
70
+ project: makeRef(projectComposer),
71
+ assignee: makeRef(contactRich),
72
+ }),
73
+ );
74
+ const _taskComposer3 = db.add(
75
+ create(Task, {
76
+ name: 'Add support for custom themes',
77
+ project: makeRef(projectComposer),
78
+ assignee: makeRef(contactJosiah),
79
+ }),
80
+ );
81
+ const _taskComposer5 = db.add(
82
+ create(Task, {
83
+ name: 'Implement community plugin',
84
+ project: makeRef(projectComposer),
85
+ assignee: makeRef(contactFred),
86
+ }),
87
+ );
88
+ const _taskComposer4 = db.add(
89
+ create(Task, {
90
+ name: 'Implement dark mode',
91
+ project: makeRef(projectComposer),
92
+ assignee: makeRef(contactRich),
93
+ }),
94
+ );
95
+ const _taskEcho1 = db.add(
96
+ create(Task, {
97
+ name: 'Implement cypher query engine',
98
+ project: makeRef(projectEcho),
99
+ assignee: makeRef(contactDima),
100
+ }),
101
+ );
102
+ const _taskEcho2 = db.add(
103
+ create(Task, {
104
+ name: 'Add schema editor',
105
+ project: makeRef(projectEcho),
106
+ assignee: makeRef(contactRich),
107
+ }),
108
+ );
109
+ const _taskDoodles1 = db.add(
110
+ create(Task, {
111
+ name: 'Add support for custom themes',
112
+ project: makeRef(projectDoodles),
113
+ assignee: makeRef(contactFred),
114
+ }),
115
+ );
116
+ const _taskDoodles2 = db.add(
117
+ create(Task, {
118
+ name: 'Implement dark mode',
119
+ project: makeRef(projectDoodles),
120
+ assignee: makeRef(contactJosiah),
121
+ }),
122
+ );
123
+
124
+ const _orgDxos = db.add(
125
+ create(Org, {
126
+ name: 'DXOS',
127
+ employees: [makeRef(contactRich), makeRef(contactJosiah), makeRef(contactDima)],
128
+ projects: [makeRef(projectEcho)],
129
+ }),
130
+ );
131
+ const _orgBraneframe = db.add(
132
+ create(Org, {
133
+ name: 'Braneframe',
134
+ employees: [makeRef(contactJosiah), makeRef(contactRich)],
135
+ projects: [makeRef(projectComposer)],
136
+ }),
137
+ );
138
+ };
139
+
140
+ // TODO(burdon): Use schema/testing.
141
+ export const createTestData = (): MockDataSource => {
142
+ const dataSource = new MockDataSource();
143
+
144
+ const contactRich = dataSource.add(Contact, {
145
+ id: ObjectId.random(),
146
+ name: 'Rich',
147
+ });
148
+ const contactJosiah = dataSource.add(Contact, {
149
+ id: ObjectId.random(),
150
+ name: 'Josiah',
151
+ });
152
+ const contactDima = dataSource.add(Contact, {
153
+ id: ObjectId.random(),
154
+ name: 'Dima',
155
+ });
156
+ const contactFred = dataSource.add(Contact, {
157
+ id: ObjectId.random(),
158
+ name: 'Fred',
159
+ });
160
+
161
+ const projectComposer = dataSource.add(Project, {
162
+ id: ObjectId.random(),
163
+ name: 'Composer',
164
+ });
165
+ const projectEcho = dataSource.add(Project, {
166
+ id: ObjectId.random(),
167
+ name: 'ECHO',
168
+ });
169
+ const projectDoodles = dataSource.add(Project, {
170
+ id: ObjectId.random(),
171
+ name: 'Doodles',
172
+ });
173
+
174
+ const _taskComposer1 = dataSource.add(Task, {
175
+ id: ObjectId.random(),
176
+ name: 'Optimize startup performance',
177
+ project: makeRef(projectComposer),
178
+ assignee: makeRef(contactJosiah),
179
+ });
180
+ const _taskComposer2 = dataSource.add(Task, {
181
+ id: ObjectId.random(),
182
+ name: 'Create form builder',
183
+ project: makeRef(projectComposer),
184
+ assignee: makeRef(contactRich),
185
+ });
186
+ const _taskComposer3 = dataSource.add(Task, {
187
+ id: ObjectId.random(),
188
+ name: 'Add support for custom themes',
189
+ project: makeRef(projectComposer),
190
+ assignee: makeRef(contactJosiah),
191
+ });
192
+ const _taskComposer5 = dataSource.add(Task, {
193
+ id: ObjectId.random(),
194
+ name: 'Implement community plugin',
195
+ project: makeRef(projectComposer),
196
+ assignee: makeRef(contactFred),
197
+ });
198
+ const _taskComposer4 = dataSource.add(Task, {
199
+ id: ObjectId.random(),
200
+ name: 'Implement dark mode',
201
+ project: makeRef(projectComposer),
202
+ assignee: makeRef(contactRich),
203
+ });
204
+ const _taskEcho1 = dataSource.add(Task, {
205
+ id: ObjectId.random(),
206
+ name: 'Implement cypher query engine',
207
+ project: makeRef(projectEcho),
208
+ assignee: makeRef(contactDima),
209
+ });
210
+ const _taskEcho2 = dataSource.add(Task, {
211
+ id: ObjectId.random(),
212
+ name: 'Add schema editor',
213
+ project: makeRef(projectEcho),
214
+ assignee: makeRef(contactRich),
215
+ });
216
+ const _taskDoodles1 = dataSource.add(Task, {
217
+ id: ObjectId.random(),
218
+ name: 'Add support for custom themes',
219
+ project: makeRef(projectDoodles),
220
+ assignee: makeRef(contactFred),
221
+ });
222
+ const _taskDoodles2 = dataSource.add(Task, {
223
+ id: ObjectId.random(),
224
+ name: 'Implement dark mode',
225
+ project: makeRef(projectDoodles),
226
+ assignee: makeRef(contactJosiah),
227
+ });
228
+
229
+ const _orgDxos = dataSource.add(Org, {
230
+ id: ObjectId.random(),
231
+ name: 'DXOS',
232
+ employees: [makeRef(contactRich), makeRef(contactJosiah), makeRef(contactDima)],
233
+ projects: [makeRef(projectEcho)],
234
+ });
235
+ const _orgBraneframe = dataSource.add(Org, {
236
+ id: ObjectId.random(),
237
+ name: 'Braneframe',
238
+ employees: [makeRef(contactJosiah), makeRef(contactRich)],
239
+ projects: [makeRef(projectComposer)],
240
+ });
241
+ dataSource.computeGraph();
242
+
243
+ return dataSource;
244
+ };
245
+
246
+ export class MockDataSource implements DataSource {
247
+ objects: Record<
248
+ string,
249
+ {
250
+ schema: S.Schema.All;
251
+ typeDxn: string;
252
+ id: string;
253
+ data: unknown;
254
+ }
255
+ > = {};
256
+
257
+ schema: Record<
258
+ string,
259
+ {
260
+ json: JsonSchemaType;
261
+ instance: S.Schema.All;
262
+ }
263
+ > = {};
264
+
265
+ nodes: Node[] = [];
266
+ relationships: Relationship[] = [];
267
+
268
+ async getNodes({ label }: { label?: string }) {
269
+ return this.nodes.filter((node) => !label || node.label === label);
270
+ }
271
+
272
+ async getRelationships({ label }: { label?: string }) {
273
+ return this.relationships.filter((relationship) => !label || relationship.label === label);
274
+ }
275
+
276
+ add<S extends S.Schema.All>(schema: S, data: S.Schema.Type<S>) {
277
+ invariant(typeof data.id === 'string', 'Data must have an id');
278
+ const jsonSchema = toJsonSchema(schema);
279
+ if (!this.schema[jsonSchema.$id!]) {
280
+ this.schema[jsonSchema.$id!] = {
281
+ json: jsonSchema,
282
+ instance: schema,
283
+ };
284
+ }
285
+
286
+ const typenameDxn = jsonSchema.$id ?? raise(new Error('Schema must have $id'));
287
+
288
+ this.objects[data.id] = {
289
+ schema,
290
+ id: data.id,
291
+ typeDxn: typenameDxn,
292
+ data,
293
+ };
294
+
295
+ return data;
296
+ }
297
+
298
+ computeGraph() {
299
+ this.nodes = [];
300
+ this.relationships = [];
301
+
302
+ for (const object of Object.values(this.objects)) {
303
+ this.nodes.push({
304
+ kind: 'node',
305
+ id: object.id,
306
+ label: formatNodeLabel(object.typeDxn),
307
+ properties: object.data as any,
308
+ });
309
+ }
310
+
311
+ for (const object of Object.values(this.objects)) {
312
+ const schema = this.schema[object.typeDxn].json;
313
+ const typenameDxn = schema.$id ?? raise(new Error('Schema must have $id'));
314
+
315
+ for (const [name, propSchema] of Object.entries(schema.properties!)) {
316
+ if (propSchema.$id === JSON_SCHEMA_ECHO_REF_ID) {
317
+ const label = formatInferredRelationshipLabel(typenameDxn, name);
318
+
319
+ const source = this.nodes.find((node) => node.id === object.id);
320
+ if (!source) {
321
+ continue;
322
+ }
323
+
324
+ const { id: targetId } = (object.data as any)[name].target;
325
+
326
+ const target = this.nodes.find((node) => node.id === targetId);
327
+ if (!target) {
328
+ continue;
329
+ }
330
+
331
+ this.relationships.push({
332
+ kind: 'relationship',
333
+ id: `${object.id}-${name}-${target.id}`,
334
+ label,
335
+ properties: {},
336
+ source,
337
+ target,
338
+ });
339
+ } else if (propSchema.type === 'array' && propSchema.items?.$id === JSON_SCHEMA_ECHO_REF_ID) {
340
+ const label = formatInferredRelationshipLabel(typenameDxn, name);
341
+
342
+ const source = this.nodes.find((node) => node.id === object.id);
343
+ if (!source) {
344
+ continue;
345
+ }
346
+
347
+ for (const { id: targetId } of (object.data as any)[name].map((x: Ref<any>) => x.target)) {
348
+ const target = this.nodes.find((node) => node.id === targetId);
349
+ if (!target) {
350
+ continue;
351
+ }
352
+
353
+ this.relationships.push({
354
+ kind: 'relationship',
355
+ id: `${object.id}-${name}-${targetId}`,
356
+ label,
357
+ properties: {},
358
+ source,
359
+ target,
360
+ });
361
+ }
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
@@ -0,0 +1,80 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Schema as S } from '@effect/schema';
6
+
7
+ import { EntityKind, ObjectAnnotationId, Ref } from '@dxos/echo-schema';
8
+
9
+ // TODO(burdon): Reconcile with sdk/schema/testing.
10
+ // TODO(burdon): Convert interface to type.
11
+
12
+ // TODO(dmaretskyi): Extract.
13
+ export const ObjectId = S.String.annotations({ description: 'The unique object identifier.' });
14
+
15
+ export const Contact = S.Struct({
16
+ id: ObjectId,
17
+ name: S.String.annotations({ description: 'The name of the person.' }),
18
+ email: S.optional(S.String).annotations({ description: 'Email address.' }),
19
+ })
20
+ .pipe(S.mutable)
21
+ .annotations({
22
+ [ObjectAnnotationId]: {
23
+ kind: EntityKind.Object,
24
+ typename: 'example.com/type/Contact',
25
+ version: '0.1.0',
26
+ },
27
+ description: 'Contact information.',
28
+ });
29
+ export interface Contact extends S.Schema.Type<typeof Contact> {}
30
+
31
+ export const Project = S.Struct({
32
+ id: ObjectId,
33
+ name: S.String.annotations({ description: 'The name of the project.' }),
34
+ description: S.optional(S.String).annotations({ description: 'The description of the project.' }),
35
+ })
36
+ .pipe(S.mutable)
37
+ .annotations({
38
+ [ObjectAnnotationId]: {
39
+ kind: EntityKind.Object,
40
+ typename: 'example.com/type/Project',
41
+ version: '0.1.0',
42
+ },
43
+ description: 'Contact information.',
44
+ });
45
+ export interface Project extends S.Schema.Type<typeof Project> {}
46
+
47
+ export const Task = S.Struct({
48
+ id: ObjectId,
49
+ name: S.String.annotations({ description: 'The name of the task.' }),
50
+ description: S.optional(S.String).annotations({ description: 'The description of the task.' }),
51
+ project: Ref(Project),
52
+ assignee: Ref(Contact),
53
+ })
54
+ .pipe(S.mutable)
55
+ .annotations({
56
+ [ObjectAnnotationId]: {
57
+ kind: EntityKind.Object,
58
+ typename: 'example.com/type/Task',
59
+ version: '0.1.0',
60
+ },
61
+ description: 'Contact information.',
62
+ });
63
+ export interface Task extends S.Schema.Type<typeof Task> {}
64
+
65
+ export const Org = S.Struct({
66
+ id: ObjectId,
67
+ name: S.String.annotations({ description: 'The name of the organization.' }),
68
+ projects: S.Array(Ref(Project)),
69
+ employees: S.Array(Ref(Contact)),
70
+ })
71
+ .pipe(S.mutable)
72
+ .annotations({
73
+ [ObjectAnnotationId]: {
74
+ kind: EntityKind.Object,
75
+ typename: 'example.com/type/Org',
76
+ version: '0.1.0',
77
+ },
78
+ description: 'Contact information.',
79
+ });
80
+ export interface Org extends S.Schema.Type<typeof Org> {}
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './trim';
@@ -0,0 +1,15 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ /**
6
+ * Trims multiline strings.
7
+ */
8
+ // TODO(burdon): Move to util.
9
+ export const trim = (strings: TemplateStringsArray, ...values: any[]) => {
10
+ const combined = strings.reduce((acc, str, i) => {
11
+ return acc + str + (values[i] || '');
12
+ }, '');
13
+
14
+ return combined.replace('/^\n/', '').replace(/\n$/, '');
15
+ };